/*++
Copyright (c) 1998  Microsoft Corporation

Module Name:  group.hxx

Abstract:

    Manages cache group.
    
Author:
    Danpo Zhang (DanpoZ) 02-08-98
--*/

#include <cache.hxx>

GroupMgr::GroupMgr()
{
    _pContainer = NULL;
}

GroupMgr::~GroupMgr()
{
    if( _pContainer )
    {
        _pContainer->Release(FALSE);
    }
}

BOOL
GroupMgr::Init(URL_CONTAINER* pCont)
{
    BOOL fRet = TRUE;

    if( pCont )
    {
        _pContainer = pCont;
        _pContainer->AddRef();
    }
    else
    {
        SetLastError(ERROR_INTERNET_INTERNAL_ERROR);
        fRet = FALSE;
    }

    return fRet;
}

DWORD
GroupMgr::CreateGroup(DWORD dwFlags, GROUPID* pGID)
{
    INET_ASSERT(_pContainer);
    INET_ASSERT(pGID);

    BOOL            fMustUnlock;
    DWORD           dwError;
    GROUP_ENTRY*    pGroupEntry = NULL;

    *pGID = 0;

    if( !_pContainer->LockContainer(&fMustUnlock) )
    {
        dwError = ERROR_INTERNET_INTERNAL_ERROR;
        goto exit;    
    }

    if( dwFlags & CACHEGROUP_FLAG_GIDONLY )
    {
        // only needs to return GID, no group needs to be created
        *pGID = ObtainNewGID();
        if( *pGID )
            dwError = ERROR_SUCCESS;
        else
            dwError = ERROR_INTERNET_INTERNAL_ERROR;
            

        goto exit;
    }

    //
    // find the first available entry by using FindEntry()
    // passing gid = 0 means looking for empty entry 
    // passing TRUE means create new page if no entry available
    //
    dwError = FindEntry(0, &pGroupEntry, TRUE );
    if( dwError != ERROR_SUCCESS )
    {
        goto exit;
    }

    // get a new gid
    *pGID = ObtainNewGID();

    if( *pGID )
    {
        // insert gid into the first available entry
        
        // set the sticky bit for non purgable group
        if( dwFlags & CACHEGROUP_FLAG_NONPURGEABLE )
        {
            *pGID = SetStickyBit(*pGID);
        }

        pGroupEntry->gid = *pGID;
        pGroupEntry->dwGroupFlags = dwFlags;
        dwError = ERROR_SUCCESS;
    } 
   
exit: 
    if( fMustUnlock )
    {
        _pContainer->UnlockContainer();
    }

    return dwError;
}


DWORD
GroupMgr::CreateDefaultGroups()
{
    
    INET_ASSERT(_pContainer);

    BOOL            fMustUnlock;
    DWORD           dwError;
    GROUP_ENTRY*    pGroupEntry = NULL;
    DWORD           dwOffsetHead = 0;

    if( !_pContainer->LockContainer(&fMustUnlock) )
    {
        dwError = ERROR_INTERNET_INTERNAL_ERROR;
        goto exit;    
    }

    if(    GetHeaderData( CACHE_HEADER_DATA_ROOTGROUP_OFFSET, &dwOffsetHead)
        && dwOffsetHead )
    {
        BOOL fBadHead = FALSE;

        // dwOffsetHead may point to a page which has not actually mapped in
        if( _pContainer->_UrlObjStorage->IsBadGroupOffset(dwOffsetHead) ) 
        {
            fBadHead = TRUE;
        }
        else
        {
            
            // if offset is too big, invalid
            FILEMAP_ENTRY* pFM = NULL;

            pFM = (FILEMAP_ENTRY*) 
                    (*_pContainer->_UrlObjStorage->GetHeapStart() + 
                    dwOffsetHead - sizeof(FILEMAP_ENTRY) );                                   
            if(pFM->dwSig != SIG_ALLOC || !pFM->nBlocks )
            {
                fBadHead = TRUE;
            }
        }
            
        if( fBadHead )
        {
            // dwOffsetHead is invalid, reset!
            SetHeaderData(CACHE_HEADER_DATA_ROOTGROUP_OFFSET, 0);
        }
    }

    // if already created, just return success
    dwError = FindEntry(CACHEGROUP_ID_BUILTIN_STICKY, &pGroupEntry, FALSE);
    if( dwError == ERROR_SUCCESS )
    {
        goto exit;
    }

    //
    // not found, need to create new default groups
    //
    // find the first available entry by using FindEntry()
    // passing gid = 0 means looking for empty entry 
    // passing TRUE means create new page if no entry available
    //
    dwError = FindEntry(0, &pGroupEntry, TRUE );
    if( dwError != ERROR_SUCCESS )
    {
        goto exit;
    }

    // set the sticky bit for non purgable group
    pGroupEntry->gid = CACHEGROUP_ID_BUILTIN_STICKY;
    pGroupEntry->dwGroupFlags = CACHEGROUP_FLAG_NONPURGEABLE;
    dwError = ERROR_SUCCESS;
   
exit: 
    if( fMustUnlock )
    {
        _pContainer->UnlockContainer();
    }

    return dwError;
}


DWORD
GroupMgr::DeleteGroup(GROUPID gid, DWORD dwFlags)
{
    INET_ASSERT(_pContainer);
    INET_ASSERT(gid);

    BOOL                fMustUnlock;
    DWORD               dwError;
    GROUP_ENTRY*        pGroupEntry = NULL;
    GROUP_DATA_ENTRY*   pData = NULL;
    DWORD               hUrlFindHandle = 0;
    URL_FILEMAP_ENTRY*  pUrlEntry = 0;
    DWORD               dwFindFilter;
    HASH_ITEM*          pItem = NULL; 


    if( !_pContainer->LockContainer(&fMustUnlock) )
    {
        dwError = ERROR_INTERNET_INTERNAL_ERROR;
        goto exit;    
    }


    // find the first available entry
    dwError = FindEntry(gid, &pGroupEntry, FALSE);
    if( dwError != ERROR_SUCCESS )
    {
        goto exit;
    }
     

    // Look for all the url associated with this group
    // mark the groupid to 0
    hUrlFindHandle = _pContainer->GetInitialFindHandle();       

    // set up find filter (do not care about cookie/history)
    dwFindFilter = URLCACHE_FIND_DEFAULT_FILTER 
                    & ~COOKIE_CACHE_ENTRY 
                    & ~URLHISTORY_CACHE_ENTRY;
    
    //
    // loop find all url belongs to this group
    // WARNING: this can be slow!
    //
    do 
    {
        // next url in this group
        pUrlEntry = (URL_FILEMAP_ENTRY*)
                        _pContainer->_UrlObjStorage->FindNextEntry( 
                                &hUrlFindHandle, dwFindFilter, gid); 

        if( pUrlEntry )
        {
            INET_ASSERT(hUrlFindHandle);
            pItem = (HASH_ITEM*)(
                    (LPBYTE) *_pContainer->_UrlObjStorage->GetHeapStart() +
                    hUrlFindHandle );

            if( pItem->HasMultiGroup() )
            {
                //
                // examing the group list and remove this group
                // from the list
                //
                DWORD       dwNewHeaderOffset       = pUrlEntry->dwGroupOffset;
                DWORD       dwGroupEntryOffset      = PtrDiff32(pGroupEntry, *_pContainer->_UrlObjStorage->GetHeapStart());

                //
                // find the to be deleted group entry in the list
                // of groups associated with this url, we need to
                // fix this by removing the to be dead group from 
                // the list
                //
                DWORD Error = RemoveFromGroupList(
                    pUrlEntry->dwGroupOffset, 
                    dwGroupEntryOffset,
                    &dwNewHeaderOffset 
                );
                    
            
                //
                // found the entry and head offset has been changed
                //
                if( Error == ERROR_SUCCESS && 
                    dwNewHeaderOffset != pUrlEntry->dwGroupOffset )
                {
                    pUrlEntry->dwGroupOffset = dwNewHeaderOffset;
               
                    // 
                    // no more group associated with this url 
                    // let's update the hash flags 
                    //
                    if( !dwNewHeaderOffset )
                    {
                        pItem->ClearMultGroup();
                        pItem->ClearGroup();
                    }
                }

                // sticky bit
                if(!pUrlEntry->dwExemptDelta && IsStickyGroup(gid) )
                {
                    //
                    // unset sticky bit for this url IFF 
                    // 1) we are about to delete the last group of this url
                    // 2) there is no more sticky group associated with this
                    //    url other than the to be deleted group
                    //
                    if( !pUrlEntry->dwGroupOffset ||
                        (  pUrlEntry->dwGroupOffset &&
                           NoMoreStickyEntryOnList(pUrlEntry->dwGroupOffset)))
                    {
                    
                        _pContainer->UpdateStickness(
                            pUrlEntry,
                            URLCACHE_OP_UNSET_STICKY,
                            hUrlFindHandle        
                        );
                    }
                }
            }
            else
            {
                //
                // do not move the url entry now, so we just
                // need to reset the GroupOffset and re-exam the
                // stick bit
                //
                pUrlEntry->dwGroupOffset = 0;

                // sticky bit
                if(!pUrlEntry->dwExemptDelta && IsStickyGroup(gid) )
                {

                    _pContainer->UpdateStickness(
                        pUrlEntry,
                        URLCACHE_OP_UNSET_STICKY,
                        hUrlFindHandle        
                    );
                }

            }


            if( dwFlags & CACHEGROUP_FLAG_FLUSHURL_ONDELETE)
            {
                //
                // Container's DeleteUrlEntry method takes two 
                // param, the url entry and hash item.
                // The hUrlFindHandle actually contains the
                // offset of the Hash Item, so we can get 
                // the hash item from there. 
                //

                // if this url belongs to other groups, 
                // do not delete it
                if( !pItem->HasMultiGroup() )
                {
                    _pContainer->DeleteUrlEntry(pUrlEntry, pItem, SIG_DELETE);
                }
            }

        } // find next url
    } while( pUrlEntry);
                    
    // if data entry exists, we should free them as well 
    if( pGroupEntry->dwGroupNameOffset )
    {
        dwError = FindDataEntry(pGroupEntry, &pData, FALSE); 
        if( dwError == ERROR_SUCCESS )
        {
            FreeDataEntry(pData);
        }
    }

    memset(pGroupEntry, 0, sizeof(GROUP_ENTRY) );
    dwError = ERROR_SUCCESS;

exit: 
    if( fMustUnlock )
    {
        _pContainer->UnlockContainer();
    }

    return dwError;
}



DWORD
GroupMgr::GetGroup(
    GROUPID                             gid, 
    DWORD                               dwAttrib, 
    INTERNET_CACHE_GROUP_INFOA*         pOutGroupInfo, 
    DWORD*                              pdwOutGroupInfoSize
)
{
    INET_ASSERT(_pContainer);
    INET_ASSERT(gid && pOutGroupInfo && pdwOutGroupInfoSize);

    BOOL            fMustUnlock;
    DWORD           dwError;
    GROUP_ENTRY*    pGroupEntry = NULL;

    if( !_pContainer->LockContainer(&fMustUnlock) )
    {
        dwError = ERROR_INTERNET_INTERNAL_ERROR; 
        goto exit;    
    }

    
    *pdwOutGroupInfoSize = 0;

    // find the entry
    dwError = FindEntry(gid, &pGroupEntry, FALSE); 
    if( dwError != ERROR_SUCCESS )
    {
        goto exit;
    }

    // init out param
    memset(pOutGroupInfo, 0, sizeof(INTERNET_CACHE_GROUP_INFOA) );

    // copy over GROUP_ENTRY -> GROUP_INFO
    Translate(
            dwAttrib,
            pOutGroupInfo, 
            pGroupEntry, 
            GROUP_ENTRY_TO_INFO, 
            pdwOutGroupInfoSize 
    ); 
    dwError = ERROR_SUCCESS; 

exit: 
    if( fMustUnlock )
    {
        _pContainer->UnlockContainer();
    }

    return dwError;
}


DWORD
GroupMgr::SetGroup(
    GROUPID                             gid, 
    DWORD                               dwAttrib, 
    INTERNET_CACHE_GROUP_INFOA*         pGroupInfo 
)
{
    INET_ASSERT(_pContainer);
    INET_ASSERT(pGroupInfo && gid);

    BOOL  fMustUnlock;
    DWORD dwError;
    GROUP_ENTRY* pGroupEntry;

    if( !_pContainer->LockContainer(&fMustUnlock) )
    {
        dwError = GetLastError();
        goto Cleanup;    
    }

    pGroupEntry = NULL;

    INET_ASSERT(pGroupInfo);

    if( dwAttrib & ~(CACHEGROUP_READWRITE_MASK) ) 
    {
        //
        // read only fields are being requested
        //
        dwError = ERROR_INVALID_PARAMETER;
        goto Cleanup;
    }

    if( (dwAttrib & CACHEGROUP_ATTRIBUTE_GROUPNAME) &&
        (strlen(pGroupInfo->szGroupName) >= GROUPNAME_MAX_LENGTH ) ) 
    {
        //
        // name too long, exceed the buffer limit 
        //
        dwError = ERROR_INVALID_PARAMETER;
        goto Cleanup;
    }

    // find the entry
    dwError = FindEntry(gid, &pGroupEntry, FALSE);
    if( dwError != ERROR_SUCCESS )
    {
        goto Cleanup;
    }

    // copy over GROUP_INFO -> GROUP_ENTRY
    Translate(
            dwAttrib,
            pGroupInfo, 
            pGroupEntry, 
            GROUP_INFO_TO_ENTRY, 
            0 
    ); 
    dwError = ERROR_SUCCESS;
    
Cleanup:
    if( fMustUnlock )
    {
        _pContainer->UnlockContainer();
    }

    return dwError; 
}


DWORD
GroupMgr::GetNextGroup(
    DWORD*                          pdwLastItemOffset, 
    GROUPID*                        pOutGroupId
)
{
    INET_ASSERT(_pContainer);
    INET_ASSERT(pOutGroupId);

    BOOL            fMustUnlock;
    BOOL            fEndOfGroups;
    GROUP_ENTRY*    pGroupEntry;
    DWORD           dwNewOffset;
    DWORD           dwError;

    if( !_pContainer->LockContainer(&fMustUnlock) )
    {
        dwError = ERROR_INTERNET_INTERNAL_ERROR;
        fEndOfGroups = TRUE;
        goto Cleanup;    
    }

    pGroupEntry = NULL;
    dwNewOffset = 0;
    fEndOfGroups = FALSE;

    if( *pdwLastItemOffset == 0 )
    {
        // get root
        dwError = FindRootEntry(&pGroupEntry, FALSE );
        if( dwError != ERROR_SUCCESS )
        {
            //
            // new find and we can not get the root entry
            // this means there are no group at all. 
            //
            fEndOfGroups = TRUE;
            goto Cleanup;
        }
    } // IF: no previous offset, this is a new Find 

    else if( *pdwLastItemOffset == OFFSET_NO_MORE_GROUP )
    {
        // this group of search has completed already
        fEndOfGroups = TRUE;
        dwError = ERROR_FILE_NOT_FOUND;
        goto Cleanup;

    } // ELSE IF: previous FindNext has already reached the end of the groups 

    else
    {
        //
        // use the offset to jump to the last returned item's entry  
        //
        pGroupEntry = (GROUP_ENTRY*) 
            (*_pContainer->_UrlObjStorage->GetHeapStart() + *pdwLastItemOffset);                                   
        //
        // one step forward 
        //
        INET_ASSERT(pGroupEntry);                      // can't be null
        INET_ASSERT( !IsIndexToNewPage(pGroupEntry) ); // can't be index item
        pGroupEntry++;

    } // ELSE: walk to the item which has been returned by previous FindNext()


    // loop for next entry 
    while(pGroupEntry)
    {
        //
        // if this entry is the last one of the page
        // it contains offset pointing to the next page
        //
        if( IsIndexToNewPage(pGroupEntry) )
        {
            //
            // BUGBUG
            // we currently use dwFlags to indicating if
            // this is pointing to the next offset
            //
            if( pGroupEntry->dwGroupFlags )
            {
                //
                // walk to next page
                //
                pGroupEntry = (GROUP_ENTRY*)
                        (   *_pContainer->_UrlObjStorage->GetHeapStart() 
                          + pGroupEntry->dwGroupFlags );                                   
            } // IF: index entry point to next page

            else
            {
                //
                // we are done 
                //
                fEndOfGroups = TRUE;
                dwError = ERROR_FILE_NOT_FOUND;
                break; 

            } // ELSE: index page contains nothing (this is the last page)

        } // special case: current entry is the index(point to next page)


        // 
        // using gid to test if the entry is empty, if not, 
        // walk to the next entry  
        //
        if( !pGroupEntry->gid )
        {
            pGroupEntry++;
        } 
        else
        {
            break;    
        }

    } // while(pGroupEntry)
    

Cleanup:
    // update LastItemOffset
    if (!fEndOfGroups
        && pGroupEntry)
    {
        LPBYTE      lpbBase = *_pContainer->_UrlObjStorage->GetHeapStart(); 
        dwNewOffset = PtrDiff32(pGroupEntry, lpbBase);
        *pdwLastItemOffset = dwNewOffset;

        // copy over GROUP_ENTRY -> GROUP_INFO
        *pOutGroupId = pGroupEntry->gid;
        dwError = ERROR_SUCCESS;

    } // IF:  find the item

    else
    {
        *pdwLastItemOffset = OFFSET_NO_MORE_GROUP;
        dwError = ERROR_FILE_NOT_FOUND;
    } // ELSE: not find 

    if( fMustUnlock )
    {
        _pContainer->UnlockContainer();
    }

    return dwError;
}


DWORD
GroupMgr::FindRootEntry(
    GROUP_ENTRY** ppOut,        // OUT: first empty entry
    BOOL fCreate                // allocate new page if needed
)
{
    INET_ASSERT(ppOut);
    *ppOut = NULL;
    
    GROUPS_ALLOC_FILEMAP_ENTRY* pPage = NULL;
    DWORD                       dwError;
    DWORD                       dwOffsetToRootEntry = 0;

    // get base offset 
    if( GetHeaderData( CACHE_HEADER_DATA_ROOTGROUP_OFFSET, &dwOffsetToRootEntry))
    {
        if( !dwOffsetToRootEntry && fCreate )
        {
            dwError = CreateNewPage(&dwOffsetToRootEntry, TRUE);

            if( dwError != ERROR_SUCCESS)
            {
                goto Cleanup;
            }
        } 
        else if( !dwOffsetToRootEntry && !fCreate )
        {
            //
            // there is no offset infomation on the mem file 
            // however, the flag says do not create a new page
            // failure is the only option here
            //
            dwError = ERROR_FILE_NOT_FOUND;
            goto Cleanup;
        } 

    } // IF: retrieve base offset

    else
    {
        dwError = ERROR_INTERNET_INTERNAL_ERROR;
        goto Cleanup; 

    } // ELSE: failed to get base offset
    

    // 
    // At this point, we should either:
    //  1. retrieved valid dwOffsetToRootEntry or 
    //  2. get the new dwOffsetToRootEntry via CreateNewPage() call  
    //
    INET_ASSERT( dwOffsetToRootEntry );
    *ppOut =  (GROUP_ENTRY*) 
        ( *_pContainer->_UrlObjStorage->GetHeapStart() + dwOffsetToRootEntry);                                   
    dwError = ERROR_SUCCESS;

Cleanup:
    return dwError; 
}



DWORD
GroupMgr::FindEntry(
    GROUPID         gid,          // gid, 0 means find first empty seat
    GROUP_ENTRY**   ppOut,        // OUT: entry with gid specified
    BOOL            fCreate       // allocate new page if needed 
                                  // (applied for searching empty seat only)
)
{
    INET_ASSERT(ppOut);

    // fCreate can only be associated with gid == 0
    INET_ASSERT( (fCreate && !gid ) || (!fCreate && gid ) );

    GROUP_ENTRY*    pGroupEntry = NULL;
    DWORD           dwError;

    // get Root Entry
    dwError = FindRootEntry(&pGroupEntry, fCreate);
    if( dwError != ERROR_SUCCESS )
    {
        dwError = ERROR_INTERNET_INTERNAL_ERROR;
        goto Cleanup;
    } // failed to get the root entry


    INET_ASSERT(pGroupEntry); // pGroupEntry should be available now

    while(1)
    {
        // special case for end of this page
        if( IsIndexToNewPage(pGroupEntry) )
        {
            //
            // BUGBUG
            // we currently use the dwFlags to indicating
            // if this is pointing to the next offset
            //
            if( pGroupEntry->dwGroupFlags )
            {
                // walk to next page
                pGroupEntry = (GROUP_ENTRY*)
                        (   *_pContainer->_UrlObjStorage->GetHeapStart()
                           + pGroupEntry->dwGroupFlags );

            } // IF: index entry points to next page
    
            else if( fCreate)
            {
//////////////////////////////////////////////////////////////////
// BEGIN WARNING: The file might be grown and remapped, so all  //
// pointers into the file before this point may be invalidated. //
//////////////////////////////////////////////////////////////////

                DWORD dwOffsetToFirstEntry = 0;
                LPBYTE  lpbBase = NULL;

                // remember the old offset for pGroupEntry
                DWORD_PTR dwpEntryOffset = PtrDifference(pGroupEntry, *_pContainer->_UrlObjStorage->GetHeapStart());

                // create new page!
                dwError = CreateNewPage(&dwOffsetToFirstEntry, FALSE);
                if( dwError != ERROR_SUCCESS )
                {
                    goto Cleanup;
                }

                // recalculate pGroupEntry using the offset remembered 
                lpbBase = *_pContainer->_UrlObjStorage->GetHeapStart(); 
                pGroupEntry = (GROUP_ENTRY*)(lpbBase + dwpEntryOffset);

//////////////////////////////////////////////////////////////////
// END WARNING: The file might be grown and remapped, so all    //
// pointers into the file before this point may be invalidated. //
//////////////////////////////////////////////////////////////////

                //
                // pGroupEntry currently is the index item, insert 
                // the offset of the first item to the newly created page
                //
                pGroupEntry->dwGroupFlags = dwOffsetToFirstEntry;

                // walk to the new page 
                pGroupEntry = (GROUP_ENTRY*)(lpbBase + dwOffsetToFirstEntry);


            } // ELSE IF: index entry not point to new page, fCreate is
              //          set, a new page is being created  

            else
            {
                // this is the end of all groups, item still not found, 
                dwError = ERROR_FILE_NOT_FOUND;
                break;

            } // ELSE: index entry not point to new page, fCreate not set

        } // IF: this entry is an index entry


        //
        // now pGroupEntry must point to a normal group entry 
        //
        INET_ASSERT( !IsIndexToNewPage(pGroupEntry) );

        if( pGroupEntry->gid != gid )
        {
            // not found, walk to next entry
            pGroupEntry++;
        } 
        else
        {
            // found entry
            dwError = ERROR_SUCCESS;
            break;    
        }

    } // WHILE: (loop over all page)

    
Cleanup:
    if( dwError == ERROR_SUCCESS )
    {
        *ppOut = pGroupEntry;
    }
    else
    {
        *ppOut = NULL;
    }

    return dwError;
}

DWORD
GroupMgr::CreateNewPage(DWORD* dwOffsetToFirstEntry, BOOL fIsFirstPage)
{
    DWORD                           dwError;
    GROUPS_ALLOC_FILEMAP_ENTRY*     pPage = NULL;
    DWORD cbSize = sizeof(GROUPS_ALLOC_FILEMAP_ENTRY);

    pPage = (GROUPS_ALLOC_FILEMAP_ENTRY*)
            _pContainer->_UrlObjStorage->AllocateEntry(cbSize);


    if( pPage )
    {
        // clean up allocated page
        cbSize = PAGE_SIZE_FOR_GROUPS;    
        memset(pPage->pGroupBlock, 0, cbSize );

        // calculate the group base offset 
        LPBYTE lpbBase = *_pContainer->_UrlObjStorage->GetHeapStart(); 

        *dwOffsetToFirstEntry = PtrDiff32(pPage->pGroupBlock, lpbBase);

        //
        // mark the last entry as index to next page
        // (gid == GID_INDEX_TO_NEXT_PAGE) is the mark, 
        // the actual offset is stored at dwGroupFlags field
        //
        GROUP_ENTRY*    pEnd = (GROUP_ENTRY*) pPage->pGroupBlock;
        pEnd = pEnd + (GROUPS_PER_PAGE - 1);
        pEnd->gid = GID_INDEX_TO_NEXT_PAGE;

        if( fIsFirstPage )
        {
            //
            // for first page, we would have to set the offset 
            // back to the CacheHeader 
            //
            if( !SetHeaderData( 
                    CACHE_HEADER_DATA_ROOTGROUP_OFFSET, *dwOffsetToFirstEntry))
            {
                // free allocated page
                _pContainer->_UrlObjStorage->FreeEntry(pPage);
        
                // set error and go
                *dwOffsetToFirstEntry = 0;
                dwError = ERROR_INTERNET_INTERNAL_ERROR;
                goto Cleanup;

            } // IF: failed to set the offset 
        }

        // return the offset to the first entry of the new page
        dwError = ERROR_SUCCESS;

    } // IF: Allocate new page succeed

    else
    {
        dwError = ERROR_NOT_ENOUGH_MEMORY;
    } // ELSE: failed to allocate new page

Cleanup:
    return dwError;
}



GROUPID
GroupMgr::ObtainNewGID()
{
    SYSTEMTIME  st;
    DWORD   dwC[2] = {0, 0};
    GROUPID gid = 0;

    // get counter from index file
    if( GetHeaderData(CACHE_HEADER_DATA_GID_LOW,  &dwC[0]) &&
        GetHeaderData(CACHE_HEADER_DATA_GID_HIGH, &dwC[1]) )
    {
        if( !dwC[0] && !dwC[1] )
        {
            // need to get the current system time
            GetSystemTime( &st );
            SystemTimeToFileTime(&st, (FILETIME*)dwC);

        } // IF: counter not initialized 

        else
        {
            // increment
            if( dwC[0] != 0xffffffff )
            {
                dwC[0] ++;
            }
            else
            {
                dwC[0] = 0;
                dwC[1] ++;
            }
        } // ELSE: counter initialized

        // send data back to cache
        if( SetHeaderData(CACHE_HEADER_DATA_GID_LOW,  dwC[0] ) &&
            SetHeaderData(CACHE_HEADER_DATA_GID_HIGH, dwC[1] ) ) 
        {
            //memcpy(&gid, dwC, sizeof(GROUPID) );
            gid = *((GROUPID *)dwC); 
        } 
    } 
    
    // apply the mask to newly created gid
    // the first 4 bits are reserved (one bit is used for stickness)  
    return (gid & GID_MASK); 
}


BOOL
GroupMgr::Translate(
    DWORD                           dwAttrib,
    INTERNET_CACHE_GROUP_INFOA*     pGroupInfo,
    GROUP_ENTRY*                    pGroupEntry, 
    DWORD                           dwFlag,
    DWORD*                          pdwSize                           
) 
{
    INET_ASSERT(pGroupInfo && pGroupEntry);
    BOOL fRet = TRUE;
    GROUP_DATA_ENTRY*   pData = NULL;
    DWORD               dwError;

    if( dwFlag == GROUP_ENTRY_TO_INFO )
    {
        INET_ASSERT(pdwSize);

        // clear
        memset(pGroupInfo, 0, sizeof(INTERNET_CACHE_GROUP_INFOA) );
        *pdwSize = 0;

        // basic entries 
        if( dwAttrib & CACHEGROUP_ATTRIBUTE_BASIC )
        {
            pGroupInfo->dwGroupSize  = sizeof(INTERNET_CACHE_GROUP_INFOA);
            pGroupInfo->dwGroupFlags = pGroupEntry->dwGroupFlags;
            pGroupInfo->dwGroupType  = pGroupEntry->dwGroupType;
            pGroupInfo->dwDiskUsage  = (DWORD)(pGroupEntry->llDiskUsage / 1024);
            pGroupInfo->dwDiskQuota  = pGroupEntry->dwDiskQuota;
        }
        
        // user friendly name
        if( ( (dwAttrib & CACHEGROUP_ATTRIBUTE_GROUPNAME) | 
              (dwAttrib & CACHEGROUP_ATTRIBUTE_STORAGE  )  ) &&
              pGroupEntry->dwGroupNameOffset ) 
        {
            dwError = FindDataEntry(pGroupEntry, &pData, FALSE);
            if( dwError != ERROR_SUCCESS )
            {
                fRet = FALSE;
            } 
            else
            {
                DWORD dwLen = strlen(pData->szName) + 1;
                INET_ASSERT( dwLen > GROUPNAME_MAX_LENGTH );

                memcpy( pGroupInfo->szGroupName, 
                        pData->szName, 
                        dwLen );

                memcpy( pGroupInfo->dwOwnerStorage,
                        pData->dwOwnerStorage, 
                        sizeof(DWORD) * GROUP_OWNER_STORAGE_SIZE );
            }
        }

        // set size
        *pdwSize = sizeof(INTERNET_CACHE_GROUP_INFOA);
    }

    else 
    if( dwFlag == GROUP_INFO_TO_ENTRY )
    {
        // copy
        if( dwAttrib & CACHEGROUP_ATTRIBUTE_FLAG )
        {
            pGroupEntry->dwGroupFlags = pGroupInfo->dwGroupFlags;
        }

        if( dwAttrib & CACHEGROUP_ATTRIBUTE_TYPE )
        {
            pGroupEntry->dwGroupType = pGroupInfo->dwGroupType;
        }

        if( dwAttrib & CACHEGROUP_ATTRIBUTE_QUOTA )
        {
            pGroupEntry->dwDiskQuota = pGroupInfo->dwDiskQuota;
        }

        if( (dwAttrib & CACHEGROUP_ATTRIBUTE_GROUPNAME) | 
            (dwAttrib & CACHEGROUP_ATTRIBUTE_STORAGE  )  )
        {

            dwError = FindDataEntry(pGroupEntry, &pData, TRUE);
            if( dwError != ERROR_SUCCESS )
            {
                fRet = FALSE;
            } 
            else
            {
                
                if( dwAttrib & CACHEGROUP_ATTRIBUTE_GROUPNAME )  
                {
                    DWORD dwLen = strlen(pGroupInfo->szGroupName) + 1;
                    INET_ASSERT(dwLen > GROUPNAME_MAX_LENGTH);

                    memcpy( pData->szName, 
                            pGroupInfo->szGroupName, 
                            dwLen );
                }

                if( dwAttrib & CACHEGROUP_ATTRIBUTE_STORAGE ) 
                {
                    memcpy( pData->dwOwnerStorage, 
                            pGroupInfo->dwOwnerStorage,
                            sizeof(DWORD) * GROUP_OWNER_STORAGE_SIZE );
                }


                // BUGBUG
                // if both fields are set to be empty, we should free
                // the allocated data itam 
            }
        }
    }

    else
    {
        fRet = FALSE;
    }
    
    return fRet;
}

BOOL
GroupMgr::IsPageEmpty(GROUP_ENTRY* pHead)
{
    BOOL fRet = FALSE;

    GROUP_ENTRY* pGroupEntry = pHead;
    for( int i = 0; i < (GROUPS_PER_PAGE - 1); i ++)
    {
        if( pGroupEntry->gid )
        {
            break;
        }
        else
        {
            pGroupEntry++;
        }
    }

    // there is no item found on this page
    if( !pGroupEntry->gid && i == GROUPS_PER_PAGE - 1 )
    {
        fRet = TRUE; 
    }


    return fRet;
}

BOOL
GroupMgr::IsLastPage(GROUP_ENTRY* pHead)
{
    BOOL fRet = FALSE;

    GROUP_ENTRY*    pEnd = NULL;

    // jump to last item
    pEnd = pHead + GROUPS_PER_PAGE;

    //
    // the gid has to be marked as GID_INDEX_TO_NEXT_PAGE 
    // for index entry, and if the dwGroupFlags is 0, 
    // that means we are not pointing to any
    // other page, this is the last page indeed.
    //
    if( pEnd->gid == GID_INDEX_TO_NEXT_PAGE && !pEnd->dwGroupFlags )
    {
        fRet = TRUE;
    }

    return fRet;
}


BOOL
GroupMgr::FreeEmptyPages(DWORD dwFlags)
{
    INET_ASSERT(_pContainer);
    BOOL            fMustUnlock;

    BOOL            fRet = TRUE;
    GROUP_ENTRY*    pHead = NULL;
    GROUP_ENTRY*    pPrevHead = NULL;
    GROUP_ENTRY*    pEnd  = NULL;
    GROUP_ENTRY*    pTobeDeleted = NULL;
    BOOL            fFirstPage = TRUE;

    if( !_pContainer->LockContainer(&fMustUnlock) )
    {
        fRet = FALSE;
        goto Cleanup;    
    }

    // BUGBUG FindRootEntry changed the return code, check for dwError
    if( FindRootEntry(&pHead, FALSE ) )
    {
        pPrevHead = pHead; 
        while(pHead)
        {
            pTobeDeleted = NULL;

            if( IsPageEmpty(pHead) )
            {
                pTobeDeleted = pHead;

                //
                // find the offset of the next page
                // 0 which means the current page is the last one
                //
                DWORD dwOffsetNextPage = 0;
                pEnd = pHead + GROUPS_PER_PAGE;
                dwOffsetNextPage = pEnd->dwGroupFlags;

                //     
                // if the first page is to be deleted, we have to 
                // update the offset which points to the next page
                //
                if( fFirstPage)
                {
                    if( !SetHeaderData(
                        CACHE_HEADER_DATA_ROOTGROUP_OFFSET, dwOffsetNextPage))
                    {
                        fRet = FALSE;
                        goto Cleanup;
                    }
                } 
                else
                {
                
                    // 
                    // Link Prev page to Next page
                    //
                    GROUP_ENTRY* pPrevEnd = pPrevHead + GROUPS_PER_PAGE;
                    pPrevEnd->dwGroupFlags = dwOffsetNextPage;  
                }
            } 
        

            //
            // update pHead make it point to the next page 
            //
            if( !IsLastPage(pHead) )
            {
                // remember pPrev
                pPrevHead = pHead;

                // walk to next page
                pEnd = pHead + GROUPS_PER_PAGE;
                pHead = (GROUP_ENTRY*)
                        (   *_pContainer->_UrlObjStorage->GetHeapStart()
                          + pEnd->dwGroupFlags );

                // not first page anymore
                fFirstPage = FALSE;
            }
            else
            {
                // this is the last page
                pHead = NULL;
            }

            // 
            // free the tobe deleted page
            //
            if( pTobeDeleted )
            {
                GROUPS_ALLOC_FILEMAP_ENTRY*     pPage = NULL;
                pPage = (GROUPS_ALLOC_FILEMAP_ENTRY*) ((LPBYTE)pTobeDeleted - sizeof(FILEMAP_ENTRY));

                _pContainer->_UrlObjStorage->FreeEntry(pPage);
            }
        }
    }

    
Cleanup:
    if( fMustUnlock )
    {
        _pContainer->UnlockContainer();
    }
    return fRet;
}


DWORD
GroupMgr::FindDataEntry(
    GROUP_ENTRY*        pGroupEntry, 
    GROUP_DATA_ENTRY**  pOutData,
    BOOL                fCreate
)
{
    INET_ASSERT(_pContainer);
    INET_ASSERT(pGroupEntry && pOutData );
    *pOutData = NULL;

    BOOL            fMustUnlock;
    DWORD           dwError;
    LPBYTE          lpbBase = NULL;

    if( !_pContainer->LockContainer(&fMustUnlock) )
    {
        dwError = ERROR_INTERNET_INTERNAL_ERROR; 
        goto exit;    
    }

    if( pGroupEntry->dwGroupNameOffset )
    {
        lpbBase = *_pContainer->_UrlObjStorage->GetHeapStart();
        *pOutData = (GROUP_DATA_ENTRY*) (lpbBase + pGroupEntry->dwGroupNameOffset);
        dwError = ERROR_SUCCESS;
    }

    else if( fCreate)
    {
//////////////////////////////////////////////////////////////////
// BEGIN WARNING: The file might be grown and remapped, so all  //
// pointers into the file before this point may be invalidated. //
//////////////////////////////////////////////////////////////////
        // remember the old offset for pGroupEntry
        DWORD_PTR   dwpEntryOffset = PtrDifference(pGroupEntry, *_pContainer->_UrlObjStorage->GetHeapStart());

        // create new data entry
        *pOutData = GetHeadDataEntry(TRUE);
        if( *pOutData )
        {
            //
            // re-calc pGroupEntry
            //
            lpbBase = (LPBYTE) *_pContainer->_UrlObjStorage->GetHeapStart();
            pGroupEntry = (GROUP_ENTRY*)(lpbBase + dwpEntryOffset);

            //
            // set entry's filename offset field 
            //
            pGroupEntry->dwGroupNameOffset = PtrDiff32(*pOutData, lpbBase);

            // succeed
            dwError = ERROR_SUCCESS;
            
        }
        else
        {
            dwError = ERROR_INTERNET_INTERNAL_ERROR;
        }
//////////////////////////////////////////////////////////////////
// END WARNING: The file might be grown and remapped, so all    //
// pointers into the file before this point may be invalidated. //
//////////////////////////////////////////////////////////////////
    }

    else
    {
        dwError = ERROR_FILE_NOT_FOUND;
    }

exit: 
    if( fMustUnlock )
    {
        _pContainer->UnlockContainer();
    }

    if( fCreate && (dwError == ERROR_SUCCESS) )
    {
        // for new item, it's nice to mark the next link to 0
        (*pOutData)->dwOffsetNext = 0;
    }
    return dwError;
}


VOID
GroupMgr::FreeDataEntry(GROUP_DATA_ENTRY* pDataEntry)
{
    // get the head entry 
    GROUP_ENTRY*    pGroupEntry = NULL;
    DWORD dwError = FindRootEntry(&pGroupEntry, FALSE );
    if( dwError != ERROR_SUCCESS )
    {
        return;
    }

    //
    // walk to the index item whose dwGroupNameOffset 
    // contains offset the the head of free list
    //
    pGroupEntry += (GROUPS_PER_PAGE - 1);
    INET_ASSERT( pGroupEntry->gid == GID_INDEX_TO_NEXT_PAGE);

    // memset the freed data entry
    memset(pDataEntry, 0, sizeof(GROUP_DATA_ENTRY) );

    // make data item's next link points to current head
    pDataEntry->dwOffsetNext = pGroupEntry->dwGroupNameOffset;

    // make the current head to be the just freed item's offset 
    LPBYTE lpbBase = *_pContainer->_UrlObjStorage->GetHeapStart();
    pGroupEntry->dwGroupNameOffset = PtrDiff32(pDataEntry, lpbBase);
}


LPGROUP_DATA_ENTRY
GroupMgr::GetHeadDataEntry(BOOL fCreate)
{
    GROUP_DATA_ENTRY*   pDataEntry = NULL;
    GROUP_ENTRY*        pGroupEntry = NULL;         
    LPBYTE              lpbBase = NULL;

    // get the head entry 
    DWORD dwError = FindRootEntry(&pGroupEntry, FALSE );
    if( dwError != ERROR_SUCCESS )
    {
        goto exit;
    }

    // walk to the index item
    pGroupEntry += (GROUPS_PER_PAGE - 1);
    INET_ASSERT( pGroupEntry->gid == GID_INDEX_TO_NEXT_PAGE);

    // the dwGroupNameOffset contains offset the the head of free list
    if( pGroupEntry->dwGroupNameOffset)
    {
        // get the head
        lpbBase = (LPBYTE) *_pContainer->_UrlObjStorage->GetHeapStart();
        pDataEntry = (GROUP_DATA_ENTRY*) (lpbBase + pGroupEntry->dwGroupNameOffset);

        // reset head to next one
        pGroupEntry->dwGroupNameOffset = pDataEntry->dwOffsetNext;
    }   

    else if( fCreate )
    {
//////////////////////////////////////////////////////////////////
// BEGIN WARNING: The file might be grown and remapped, so all  //
// pointers into the file before this point may be invalidated. //
//////////////////////////////////////////////////////////////////

        // remember the old offset for pGroupEntry
        DWORD_PTR dwpEntryOffset = PtrDifference(pGroupEntry, *_pContainer->_UrlObjStorage->GetHeapStart());

        // create a new page
        GROUPS_ALLOC_FILEMAP_ENTRY*     pPage = NULL;
        DWORD cbSize = sizeof(GROUPS_ALLOC_FILEMAP_ENTRY);

        pPage = (GROUPS_ALLOC_FILEMAP_ENTRY*)
                _pContainer->_UrlObjStorage->AllocateEntry(cbSize);

        if( !pPage )
        {
            goto exit;
        }
    
        // memset
        memset(pPage->pGroupBlock, 0, PAGE_SIZE_FOR_GROUPS);

        lpbBase = (LPBYTE) *_pContainer->_UrlObjStorage->GetHeapStart(); 
        GROUP_DATA_ENTRY*   pHead = (GROUP_DATA_ENTRY*)pPage->pGroupBlock;
        pDataEntry = pHead;

        // init list on the newly created page
        for(int i = 0; i < GROUPS_DATA_PER_PAGE - 1; i++)
        {
            // point to next offset 
            GROUP_DATA_ENTRY* pNext = pHead + 1;
            pHead->dwOffsetNext =  PtrDiff32(pNext, lpbBase);
            pHead = pNext;
        }

        //
        // pGroupEntry needs to be re-calc! 
        //
        pGroupEntry = (GROUP_ENTRY*)(lpbBase + dwpEntryOffset);

        // 
        // pGroupEntry currently is the index entry of the first 
        // page, it's dwGroupNameOffset field points the head of 
        // the list of a free group data entry
        //
        pGroupEntry->dwGroupNameOffset = pDataEntry->dwOffsetNext;

//////////////////////////////////////////////////////////////////
// END WARNING: The file might be grown and remapped, so all    //
// pointers into the file before this point may be invalidated. //
//////////////////////////////////////////////////////////////////
    }

    else
    {
        goto exit;
    }
    
exit:
    return pDataEntry;
}

DWORD
GroupMgr::GetOffsetFromList(DWORD dwHeadOffset, GROUPID gid, DWORD* pdwOffset)
{
    DWORD dwError;
    LIST_GROUP_ENTRY*   pListGroup = NULL;
    GROUP_ENTRY*        pGroupEntry = NULL;
    
    *pdwOffset = 0;

    pListGroup = 
        _pContainer->_UrlObjStorage->ValidateListGroupOffset(dwHeadOffset); 
    if( !pListGroup )
    {
        dwError = ERROR_FILE_NOT_FOUND;
        goto Cleanup;
    }   

    while(1)
    {
        
        if(!_pContainer->_UrlObjStorage->IsBadGroupOffset(pListGroup->dwGroupOffset))
        {
            pGroupEntry = (GROUP_ENTRY*)
                        (   *_pContainer->_UrlObjStorage->GetHeapStart()
                           + pListGroup->dwGroupOffset );
        }
        else
        {
            dwError = ERROR_INTERNET_INTERNAL_ERROR;
            goto Cleanup;
        }

        if( pGroupEntry && pGroupEntry->gid == gid )
        {
            *pdwOffset = pListGroup->dwGroupOffset;
            break;
        }     

        // end of list, not found 
        if( !pListGroup->dwNext )
        {
            dwError = ERROR_FILE_NOT_FOUND;
            break;
        }

        // walk to next
        pListGroup = 
            _pContainer->_UrlObjStorage->ValidateListGroupOffset(
                pListGroup->dwNext); 

        if( !pListGroup )
        {
            dwError = ERROR_FILE_NOT_FOUND;
            goto Cleanup;
        }   
    } 

    if( *pdwOffset )    
    {
        dwError = ERROR_SUCCESS;
    }
    else
    {
        dwError = ERROR_FILE_NOT_FOUND;
    }

Cleanup:
    return dwError;
}


DWORD   
GroupMgr::CreateNewGroupList(DWORD* pdwHeadOffset)
{
    DWORD               dwError;
    
    // Find empty slot
    *pdwHeadOffset = 0;
    dwError = FindEmptySlotInListPage(pdwHeadOffset);
    if( ERROR_SUCCESS != dwError )
    {
        goto Cleanup;
    }

Cleanup:
    return dwError;
}

DWORD
GroupMgr::AddToGroupList(DWORD dwHeadOffset, DWORD dwOffset)
{
    DWORD               dwError;
    DWORD               dwEmptySlot;
    LIST_GROUP_ENTRY*   pListGroup = NULL;
    LIST_GROUP_ENTRY*   pListGroupEmpty = NULL;

    // if the item already on the list, return success
    if( IsGroupOnList(dwHeadOffset, dwOffset) )
    {
        dwError = ERROR_SUCCESS;
        goto Cleanup;
    }

    pListGroup = 
        _pContainer->_UrlObjStorage->ValidateListGroupOffset(dwHeadOffset); 
    if( !pListGroup )
    {
        dwError = ERROR_INTERNET_INTERNAL_ERROR;
        goto Cleanup;
    }

    if( !pListGroup->dwGroupOffset )
    {
        // list is empty, just need to fill up the Head
        pListGroup->dwGroupOffset = dwOffset;
    }
    else
    {
        // List is not empty, we have to walk to end of the list
        // also need to get another empty slot

//////////////////////////////////////////////////////////////////
// BEGIN WARNING: The file might be grown and remapped, so all  //
// pointers into the file before this point may be invalidated. //
//////////////////////////////////////////////////////////////////
        // remember the old offset for pListGroup
        DWORD_PTR dwpListGroupOffset = PtrDifference(pListGroup, *_pContainer->_UrlObjStorage->GetHeapStart());

        // find empty slot
        dwError = FindEmptySlotInListPage(&dwEmptySlot);
        if( ERROR_SUCCESS != dwError )
        {
            goto Cleanup;
        }


        // recalculate pListGroup using the offset remembered 
        LPBYTE      lpbBase = *_pContainer->_UrlObjStorage->GetHeapStart(); 
        pListGroup = (LIST_GROUP_ENTRY*)(lpbBase + dwpListGroupOffset);

//////////////////////////////////////////////////////////////////
// END WARNING:   The file might be grown and remapped, so all  //
// pointers into the file before this point may be invalidated. //
//////////////////////////////////////////////////////////////////
        
        // walk to end of list
        while( pListGroup->dwNext )
        {
            pListGroup = 
                _pContainer->_UrlObjStorage->ValidateListGroupOffset(
                    pListGroup->dwNext); 
            if( !pListGroup )
            {
                dwError = ERROR_INTERNET_INTERNAL_ERROR;
                goto Cleanup;
            }
        }

        // Get ListGroupEmpty Object from the empty slot
        pListGroupEmpty = 
            _pContainer->_UrlObjStorage->ValidateListGroupOffset(dwEmptySlot); 
        if( !pListGroupEmpty )
        {
            dwError = ERROR_INTERNET_INTERNAL_ERROR;
            goto Cleanup;
        }

        // assign the new offset
        pListGroupEmpty->dwGroupOffset = dwOffset;

        // append empty slot at the end of the list
        // this need to be done at last to prevent some invalid
        // object get on the list
        pListGroup->dwNext = dwEmptySlot;
    }


    dwError = ERROR_SUCCESS;

Cleanup:
    return dwError;
}

DWORD   
GroupMgr::RemoveFromGroupList(
    DWORD      dwHeadOffset, 
    DWORD      dwOffset, 
    LPDWORD    pdwNewHeadOffset
)
{
    DWORD dwError = ERROR_SUCCESS;
    LIST_GROUP_ENTRY*   pListGroup = NULL;
    LIST_GROUP_ENTRY*   pListGroupPrev = NULL;
    LPBYTE              lpbBase = NULL;

    pListGroup = 
        _pContainer->_UrlObjStorage->ValidateListGroupOffset(dwHeadOffset); 
    if( !pListGroup )
    {
        dwError = ERROR_FILE_NOT_FOUND;
        goto Cleanup;
    }   

    lpbBase = (LPBYTE) *_pContainer->_UrlObjStorage->GetHeapStart(); 

    // header is the one we need, we will have to assign new header
    if( pListGroup->dwGroupOffset == dwOffset )
    {
        // new head
        *pdwNewHeadOffset = pListGroup->dwNext;

        // empty removed head and added to free list
        pListGroup->dwGroupOffset = 0;
        pListGroup->dwNext= 0;
        AddToFreeList(pListGroup);

        // done
        dwError = ERROR_SUCCESS;
        goto Cleanup;
    }

    if( !pListGroup->dwNext )
    {
        dwError = ERROR_FILE_NOT_FOUND;
        goto Cleanup;
    }   

    pListGroupPrev = pListGroup;
    pListGroup = 
        _pContainer->_UrlObjStorage->ValidateListGroupOffset(pListGroup->dwNext); 
    if( !pListGroup)
    {
        dwError = ERROR_FILE_NOT_FOUND;
        goto Cleanup;
    }   

      
    while( pListGroup )
    {
        INET_ASSERT(pListGroup->dwGroupOffset);

        if( pListGroup->dwGroupOffset == dwOffset )
        {
            pListGroupPrev->dwNext = pListGroup->dwNext;

            // empty removed item and added it to free list
            pListGroup->dwGroupOffset = 0;
            pListGroup->dwNext= 0;
            AddToFreeList(pListGroup);

            dwError = ERROR_SUCCESS;
            break;
        }

        if( pListGroup->dwNext )
        {
            pListGroupPrev = pListGroup;
            pListGroup =  
                _pContainer->_UrlObjStorage->ValidateListGroupOffset(pListGroup->dwNext); 
        }
        else
        {
            dwError = ERROR_FILE_NOT_FOUND;
            break;

        }
    }

Cleanup:
    return dwError;
}

DWORD
GroupMgr::FindEmptySlotInListPage(DWORD* pdwOffsetToSlot)
{

    DWORD   dwError;
    DWORD   dwOffsetRoot = 0;
    LPBYTE  lpbBase = NULL;
    LIST_GROUP_ENTRY*   pListGroupFreeHead = NULL;
    LIST_GROUP_ENTRY*   pListGroupEmpty = NULL;

    if( !GetHeaderData( CACHE_HEADER_DATA_ROOT_GROUPLIST_OFFSET, &dwOffsetRoot))
    {
        dwError = ERROR_INTERNET_INTERNAL_ERROR;
        goto Cleanup; 
    } 

    if( !dwOffsetRoot)
    {
        // new page needs to be created
        dwError = CreateNewListPage(&dwOffsetRoot, TRUE);

        if( dwError != ERROR_SUCCESS)
            goto Cleanup;
    } 

    // 
    // At this point, we've got the root entry 
    //  1. retrieved valid dwOffsetToRootEntry or 
    //  2. get the new dwOffsetToRootEntry via CreateNewPage() call  
    //
    INET_ASSERT( dwOffsetRoot);

    lpbBase = (LPBYTE) *_pContainer->_UrlObjStorage->GetHeapStart();
    pListGroupFreeHead =  (LIST_GROUP_ENTRY*) (lpbBase + dwOffsetRoot);                                   
    if( !pListGroupFreeHead )
    {
        dwError = ERROR_INTERNET_INTERNAL_ERROR;
        goto Cleanup; 
    }

//////////////////////////////////////////////////////////////////
// BEGIN WARNING: The file might be grown and remapped, so all  //
// pointers into the file before this point may be invalidated. //
//////////////////////////////////////////////////////////////////
    // get the next free item from the list
    if( !pListGroupFreeHead->dwNext )
    {
        // no free slot left!, let's create a new page!

        // remember the old offset free list head entry
        DWORD_PTR dwpFreeHeadOffset = PtrDifference(pListGroupFreeHead, lpbBase);

        // create a new page
        DWORD  dwNewList;
        dwError = CreateNewListPage(&dwNewList, FALSE);

        if( dwError != ERROR_SUCCESS)
            goto Cleanup;

        // restore
        lpbBase = (LPBYTE) *_pContainer->_UrlObjStorage->GetHeapStart();
        pListGroupFreeHead =  (LIST_GROUP_ENTRY*) (lpbBase + dwpFreeHeadOffset);                                   
        //
        // add the newly created page contains a list of empty
        // slot (already chained together), now update the head 
        // of free list pointing to the head of the newly created
        // list
        //
        pListGroupFreeHead->dwNext = dwNewList;
    }
//////////////////////////////////////////////////////////////////
// END WARNING:   The file might be grown and remapped, so all  //
// pointers into the file before this point may be invalidated. //
//////////////////////////////////////////////////////////////////

     
    // get the empty slot offset
    *pdwOffsetToSlot = pListGroupFreeHead->dwNext;

    // update the free list to point to the next slot
    pListGroupEmpty = (LIST_GROUP_ENTRY*)(lpbBase + pListGroupFreeHead->dwNext);
    pListGroupFreeHead->dwNext = pListGroupEmpty->dwNext;
    
    memset(pListGroupEmpty, 0, sizeof(LIST_GROUP_ENTRY) );
    
    dwError = ERROR_SUCCESS;

Cleanup:
    return dwError;
}


DWORD
GroupMgr::CreateNewListPage(DWORD* pdwOffsetToFirstEntry, BOOL fIsFirstPage)
{
    DWORD                           dwError;
    GROUPS_ALLOC_FILEMAP_ENTRY*     pPage = NULL;
    DWORD cbSize = sizeof(GROUPS_ALLOC_FILEMAP_ENTRY);

    pPage = (GROUPS_ALLOC_FILEMAP_ENTRY*)
            _pContainer->_UrlObjStorage->AllocateEntry(cbSize);


    if( pPage )
    {
        // clean up allocated page
        cbSize = PAGE_SIZE_FOR_GROUPS;    
        memset(pPage->pGroupBlock, 0, cbSize );

        // calculate the group base offset 
        LPBYTE lpbBase = (LPBYTE) *_pContainer->_UrlObjStorage->GetHeapStart(); 

        *pdwOffsetToFirstEntry = PtrDiff32(pPage->pGroupBlock, lpbBase);

        //
        // chain all items together  
        // (Last item will have dwNext == 0 since we have alredy memset 
        //  the whole page ) 
        //
        LIST_GROUP_ENTRY*    pList = (LIST_GROUP_ENTRY*) pPage->pGroupBlock;

        for( DWORD dwi = 0; dwi < (LIST_GROUPS_PER_PAGE -1); dwi++)
        {
            pList->dwNext = PtrDiff32(pList+1, lpbBase);
            pList++ ;
        }


        if( fIsFirstPage )
        {
            //
            // for first page, we would have to set the offset 
            // back to the CacheHeader 
            //
            if( !SetHeaderData( 
                    CACHE_HEADER_DATA_ROOT_GROUPLIST_OFFSET, 
                    *pdwOffsetToFirstEntry))
            {
                // free allocated page
                _pContainer->_UrlObjStorage->FreeEntry(pPage);
        
                // set error and go
                *pdwOffsetToFirstEntry = 0;
                dwError = ERROR_INTERNET_INTERNAL_ERROR;
                goto Cleanup;

            } // IF: failed to set the offset 
        }
        
        // return the offset to the first entry of the new page
        dwError = ERROR_SUCCESS;

    } // IF: Allocate new page succeed

    else
    {
        dwError = ERROR_NOT_ENOUGH_MEMORY;
    } // ELSE: failed to allocate new page

Cleanup:
    return dwError;
}



BOOL    
GroupMgr::IsGroupOnList(DWORD dwHeadOffset, DWORD dwGrpOffset)
{
    BOOL    fRet = FALSE;
    LIST_GROUP_ENTRY*   pListGroup = NULL;

    LIST_GROUP_ENTRY   *pMilestone  = NULL; // used for detecting cycles
    unsigned long       dwNodeCount = 1;

    pListGroup = 
        _pContainer->_UrlObjStorage->ValidateListGroupOffset(dwHeadOffset); 
    if( !pListGroup )
    {
        goto Cleanup;
    }

    while( pListGroup )
    {
        //INET_ASSERT(pListGroup->dwGroupOffset);

        if( pListGroup->dwGroupOffset == dwGrpOffset )
        {
            fRet = TRUE;
            break;
        }

        if( pListGroup->dwNext )
        {
            LIST_GROUP_ENTRY*   plgTemp =  
                _pContainer->_UrlObjStorage->ValidateListGroupOffset(
                    pListGroup->dwNext); 

            // Sometimes the list is corrupted and contains a cycle
            // This is detected by comparing against the saved pointer
            // (Revisiting an earlier milestone indicates a cycle)
            if (plgTemp==pMilestone)
                break;

            // Also check (and fix) simple self-loops
            if (plgTemp==pListGroup) 
            {
                pListGroup->dwNext = 0;
                break;
            }

            // Advance to next node
            pListGroup = plgTemp;

            // Choose new milestone when node count is power of 2
            dwNodeCount++;

            if ((dwNodeCount & (dwNodeCount-1)) == 0)
                pMilestone = pListGroup;
        }
        else
        {
            break;
        }
    }

Cleanup:
    return fRet;
}


BOOL    
GroupMgr::NoMoreStickyEntryOnList(DWORD dwHeadOffset)
{
    BOOL                fRet = FALSE;
    LIST_GROUP_ENTRY*   pListGroup = NULL;
    GROUP_ENTRY*        pGroupEntry = NULL;

    pListGroup = 
        _pContainer->_UrlObjStorage->ValidateListGroupOffset(dwHeadOffset); 
    if( !pListGroup )
    {
        goto Cleanup;
    }

    while( pListGroup )
    {
        //INET_ASSERT(pListGroup->dwGroupOffset);

        // get the GroupEntry structure
        if( !_pContainer->_UrlObjStorage->IsBadGroupOffset(
                    pListGroup->dwGroupOffset) )
        {
            pGroupEntry = (GROUP_ENTRY*)
                ( *_pContainer->_UrlObjStorage->GetHeapStart() + 
                pListGroup->dwGroupOffset );

            // IsSticky?
            if( IsStickyGroup(pGroupEntry->gid) )
            {
                goto Cleanup;
            }
        } 


        // end of list
        if( !pListGroup->dwNext )
        {
            break;
        }

        // next item on list
        pListGroup =  _pContainer->_UrlObjStorage->ValidateListGroupOffset(
            pListGroup->dwNext); 
    }

    //
    // reach here means we are at end of the list and can not find
    // any sticky group, return TRUE
    //
    fRet = TRUE;

Cleanup:
    return fRet;


}


void
GroupMgr::AdjustUsageOnList(DWORD dwHeadOffset, LONGLONG llDelta)
{
    LIST_GROUP_ENTRY*   pListGroup = NULL;
    GROUP_ENTRY*        pGroupEntry = NULL;

    pListGroup = 
        _pContainer->_UrlObjStorage->ValidateListGroupOffset(dwHeadOffset); 
    if( !pListGroup )
    {
        goto Cleanup;
    }

    while( pListGroup )
    {
        // INET_ASSERT(pListGroup->dwGroupOffset);

        // get the GroupEntry structure
        if( !_pContainer->_UrlObjStorage->IsBadGroupOffset(
                    pListGroup->dwGroupOffset) )
        {
            pGroupEntry = (GROUP_ENTRY*)
                ( *_pContainer->_UrlObjStorage->GetHeapStart() + 
                pListGroup->dwGroupOffset );

            // AdjustUsage
            _pContainer->AdjustGroupUsage(pGroupEntry, llDelta);
        } 


        // end of list
        if( !pListGroup->dwNext )
        {
            goto Cleanup;
        }

        // next item on list
        pListGroup =  _pContainer->_UrlObjStorage->ValidateListGroupOffset(
            pListGroup->dwNext); 
    }

Cleanup:
    return;

}

void
GroupMgr::AddToFreeList(LIST_GROUP_ENTRY* pFreeListGroup)
{
    DWORD dwOffsetRoot  = 0;
    LIST_GROUP_ENTRY*   pFreeListHead = NULL;
    
    if( GetHeaderData( CACHE_HEADER_DATA_ROOT_GROUPLIST_OFFSET, &dwOffsetRoot))
    {

        pFreeListHead = 
            _pContainer->_UrlObjStorage->ValidateListGroupOffset(dwOffsetRoot); 
   
        if( pFreeListHead && pFreeListGroup )
        {
            pFreeListGroup->dwNext = pFreeListHead->dwNext;

            pFreeListHead->dwNext = PtrDiff32(pFreeListGroup,
                                              *_pContainer->_UrlObjStorage->GetHeapStart());
        } 
    } 
    return;
}