Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

3742 lines
101 KiB

//***************************************************************************
//
// BTR.CPP
//
// WMI disk-based B-tree implementation for repository index
//
// raymcc 15-Oct-00 Prepared for Whistler Beta 2 to reduce file count
//
//***************************************************************************
#include <windows.h>
#include <stdio.h>
#include <wbemcomn.h>
#include <reposit.h>
#include <stdlib.h>
#include <arena.h>
#include <math.h>
#include <btr.h>
#define MAX_WORD_VALUE 0xFFFF
#define MAX_TOKENS_PER_KEY 32
#define MAX_FLUSH_INTERVAL 4000
//#define MAX_PAGE_HISTORY 1024
/*
Notes:
(a) Modify allocators to special case for page-size
(b) Modify WriteIdxPage to not rewrite if no deltas
(c) ERROR_PATH_NOT_FOUND if starting enum has no presence; GPF presently
(d) Do a history of page hits and see if caching would be helpful
*/
//static WORD History[MAX_PAGE_HISTORY] = {0};
LONG g_lAllocs = 0;
//***************************************************************************
//
// _BtrMemAlloc
//
//***************************************************************************
// ok
LPVOID WINAPI _BtrMemAlloc(
SIZE_T dwBytes // number of bytes to allocate
)
{
// Lookaside for items of page size, default array size, default
// string pool size
g_lAllocs++;
return HeapAlloc(CWin32DefaultArena::GetArenaHeap(), HEAP_ZERO_MEMORY, dwBytes);
}
//***************************************************************************
//
// _BtrMemReAlloc
//
//***************************************************************************
// ok
LPVOID WINAPI _BtrMemReAlloc(
LPVOID pOriginal,
DWORD dwNewBytes
)
{
return HeapReAlloc(CWin32DefaultArena::GetArenaHeap(), HEAP_ZERO_MEMORY, pOriginal, dwNewBytes);
}
//***************************************************************************
//
// _BtrMemFree
//
//***************************************************************************
// ok
BOOL WINAPI _BtrMemFree(LPVOID pMem)
{
if (pMem == 0)
return TRUE;
g_lAllocs--;
return HeapFree(CWin32DefaultArena::GetArenaHeap(), 0, pMem);
}
//***************************************************************************
//
// CPageSource::CPageSource
//
//***************************************************************************
// ok
CPageSource::CPageSource()
{
m_dwPageSize = 0;
m_hFile = INVALID_HANDLE_VALUE;
m_dwTotalPages = 0;
m_dwLogicalRoot = 0;
m_dwNextFreePage = 0;
m_dwLastFlushTime = GetCurrentTime();
}
//***************************************************************************
//
// CPageSource::CPageSource
//
//***************************************************************************
// ok
CPageSource::~CPageSource()
{
}
//***************************************************************************
//
// CPageSource::Shutdown
//
//***************************************************************************
// ok
DWORD CPageSource::Shutdown(DWORD dwShutDownFlags)
{
#ifdef A51_STAGE_BELOW_INDEX
#else
CloseHandle(m_hFile);
#endif
m_dwPageSize = 0;
m_hFile = INVALID_HANDLE_VALUE;
m_dwTotalPages = 0;
m_dwLogicalRoot = 0;
m_dwNextFreePage = 0;
return NO_ERROR;
}
//***************************************************************************
//
// CPageSource::WriteAdminPage
//
// Rewrites the admin page. There is no need to update the pagesize,
// version, etc.
//
//***************************************************************************
// ok
DWORD CPageSource::WriteAdminPage()
{
LPDWORD pPageZero = 0;
DWORD dwRes = GetPage(0, (LPVOID *) &pPageZero);
if (dwRes)
return dwRes;
pPageZero[OFFSET_LOGICAL_ROOT] = m_dwLogicalRoot;
pPageZero[OFFSET_TOTAL_PAGES] = m_dwTotalPages;
pPageZero[OFFSET_FREE_LIST_ROOT] = m_dwNextFreePage;
dwRes = PutPage(pPageZero, PAGE_TYPE_ADMIN);
_BtrMemFree(pPageZero);
return dwRes;
}
//***************************************************************************
//
// CPageSource::SetRootPage
//
//***************************************************************************
//
DWORD CPageSource::SetRootPage(DWORD dwNewRoot)
{
m_dwLogicalRoot = dwNewRoot;
return WriteAdminPage();
}
//***************************************************************************
//
// CPageSource::Init
//
// The real "constructor" which opens the file
//
//***************************************************************************
// ok
DWORD CPageSource::Init(
DWORD dwPageSize,
LPWSTR pszFilename
#ifdef A51_STAGE_BELOW_INDEX
, CAbstractFileSource* pSource
#endif
)
{
DWORD dwLastError = 0;
m_dwPageSize = dwPageSize;
DWORD dwFileFlags = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS;
m_hFile = CreateFileW(pszFilename, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, 0,
OPEN_EXISTING, dwFileFlags, 0);
dwLastError = GetLastError();
if (m_hFile == INVALID_HANDLE_VALUE)
{
if (dwLastError != ERROR_FILE_NOT_FOUND)
return dwLastError;
// If here, we have to create a new file.
m_hFile = CreateFileW(pszFilename, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, 0,
CREATE_ALWAYS, dwFileFlags, 0);
if (m_hFile == INVALID_HANDLE_VALUE)
return GetLastError();
#ifdef A51_STAGE_BELOW_INDEX
long lRes = pSource->Register(m_hFile, A51_INDEX_FILE_ID, true,
&m_pFile);
if(lRes != ERROR_SUCCESS)
{
CloseHandle(m_hFile);
m_hFile = INVALID_HANDLE_VALUE;
return lRes;
}
#endif
// Set up initial two pages
Setup();
}
else
{
// File exists. Verify that the file is an integral number of pages based
// on the specified page size.
DWORD dwLen = SetFilePointer(m_hFile, 0, 0, FILE_END);
if (dwLen % dwPageSize != 0)
{
CloseHandle(m_hFile);
m_hFile = INVALID_HANDLE_VALUE;
return ERROR_BAD_FORMAT;
}
#ifdef A51_STAGE_BELOW_INDEX
long lRes = pSource->Register(m_hFile, A51_INDEX_FILE_ID, true,
&m_pFile);
if(lRes != ERROR_SUCCESS)
{
CloseHandle(m_hFile);
m_hFile = INVALID_HANDLE_VALUE;
return lRes;
}
#endif
}
return ReadAdminPage();
}
//***************************************************************************
//
// CPageSource::ReadAdminPage
//
//***************************************************************************
// ok
DWORD CPageSource::ReadAdminPage()
{
LPDWORD pPageZero = 0;
DWORD dwRes = 0;
dwRes = GetPage(0, (LPVOID *) &pPageZero);
if (dwRes)
return dwRes;
// Grab useful info from these amazing admin pages...
m_dwLogicalRoot = pPageZero[OFFSET_LOGICAL_ROOT];
m_dwTotalPages = pPageZero[OFFSET_TOTAL_PAGES];
m_dwNextFreePage = pPageZero[OFFSET_FREE_LIST_ROOT];
if (m_dwPageSize != pPageZero[OFFSET_PAGE_SIZE])
dwRes = ERROR_BAD_FORMAT;
else if (pPageZero[OFFSET_IMPL_VERSION] != const_CurrentVersion)
dwRes = ERROR_BAD_FORMAT;
else
dwRes = NO_ERROR;
_BtrMemFree(pPageZero);
return dwRes;
}
//***************************************************************************
//
// CPageSource::Setup
//
// Sets up the 0th page (Admin page)
//
//***************************************************************************
// ok
DWORD CPageSource::Setup()
{
DWORD dwRes;
// First two pages, admin & free list root
LPDWORD pPageZero = (LPDWORD) _BtrMemAlloc(m_dwPageSize);
if (pPageZero == 0)
{
dwRes = ERROR_NOT_ENOUGH_MEMORY;
goto Exit;
}
memset(pPageZero, 0, m_dwPageSize);
// Map the page
pPageZero[OFFSET_PAGE_TYPE] = PAGE_TYPE_ADMIN;
pPageZero[OFFSET_PAGE_ID] = 0;
pPageZero[OFFSET_NEXT_PAGE] = 0;
pPageZero[OFFSET_LOGICAL_ROOT] = 0;
pPageZero[OFFSET_FREE_LIST_ROOT] = 0;
pPageZero[OFFSET_TOTAL_PAGES] = 1;
pPageZero[OFFSET_PAGE_SIZE] = m_dwPageSize;
pPageZero[OFFSET_IMPL_VERSION] = const_CurrentVersion;
// Write it out
dwRes = PutPage(pPageZero, PAGE_TYPE_ADMIN);
if (dwRes)
goto Exit;
m_dwTotalPages = 1;
Exit:
_BtrMemFree(pPageZero);
return dwRes;
}
//***************************************************************************
//
// CPageSource::Dump
//
// Debug helper
//
//***************************************************************************
// ok
void CPageSource::Dump(FILE *f)
{
SetFilePointer(m_hFile, 0, 0, FILE_BEGIN);
LPDWORD pPage = (LPDWORD) new BYTE[m_dwPageSize];
DWORD dwPage = 0;
DWORD dwTotalKeys = 0;
fprintf(f, "---BEGIN PAGE SOURCE DUMP---\n");
fprintf(f, "In memory part:\n");
fprintf(f, " m_dwPageSize = %d (0x%X)\n", m_dwPageSize, m_dwPageSize);
fprintf(f, " m_hFile = 0x%p\n", m_hFile);
fprintf(f, " m_dwNextFreePage = %d\n", m_dwNextFreePage);
fprintf(f, " m_dwTotalPages = %d\n", m_dwTotalPages);
fprintf(f, " m_dwLogicalRoot = %d\n", m_dwLogicalRoot);
fprintf(f, "---\n");
DWORD dwTotalFree = 0;
DWORD dwOffs = 0;
while (1)
{
DWORD dwRead = 0;
BOOL bRes = ReadFile(m_hFile, pPage, m_dwPageSize, &dwRead, 0);
if (dwRead != m_dwPageSize)
break;
fprintf(f, "Dump of page %d:\n", dwPage++);
fprintf(f, " Page type = 0x%X", pPage[OFFSET_PAGE_TYPE]);
if (pPage[OFFSET_PAGE_TYPE] == PAGE_TYPE_IMPOSSIBLE)
fprintf(f, " PAGE_TYPE_IMPOSSIBLE\n");
if (pPage[OFFSET_PAGE_TYPE] == PAGE_TYPE_DELETED)
{
fprintf(f, " PAGE_TYPE_DELETED\n");
fprintf(f, " <page num check = %d>\n", pPage[1]);
fprintf(f, " <next free page = %d>\n", pPage[2]);
dwTotalFree++;
}
if (pPage[OFFSET_PAGE_TYPE] == PAGE_TYPE_ACTIVE)
{
fprintf(f, " PAGE_TYPE_ACTIVE\n");
fprintf(f, " <page num check = %d>\n", pPage[1]);
SIdxKeyTable *pKT = 0;
DWORD dwKeys = 0;
DWORD dwRes = SIdxKeyTable::Create(pPage, &pKT);
if (dwRes == 0)
{
pKT->Dump(f, &dwKeys);
pKT->Release();
dwTotalKeys += dwKeys;
}
else
{
fprintf(f, "<INVALID Page Decode>\n");
}
}
if (pPage[OFFSET_PAGE_TYPE] == PAGE_TYPE_ADMIN)
{
fprintf(f, " PAGE_TYPE_ADMIN\n");
fprintf(f, " Page Num = %d\n", pPage[1]);
fprintf(f, " Next Page = %d\n", pPage[2]);
fprintf(f, " Logical Root = %d\n", pPage[3]);
fprintf(f, " Free List Root = %d\n", pPage[4]);
fprintf(f, " Total Pages = %d\n", pPage[5]);
fprintf(f, " Page Size = %d (0x%X)\n", pPage[6], pPage[6]);
fprintf(f, " Impl Version = 0x%X\n", pPage[7]);
}
}
delete [] pPage;
fprintf(f, "Total free pages detected by scan = %d\n", dwTotalFree);
fprintf(f, "Total active keys = %d\n", dwTotalKeys);
fprintf(f, "---END PAGE DUMP---\n");
}
//***************************************************************************
//
// CPageSource::GetPage
//
// Reads an existing page; does not support seeking beyond end-of-file
//
//***************************************************************************
// ok
DWORD CPageSource::GetPage(
DWORD dwPage,
LPVOID *pPage
)
{
DWORD dwRes;
if (pPage == 0)
return ERROR_INVALID_PARAMETER;
// Allocate some memory
LPVOID pMem = _BtrMemAlloc(m_dwPageSize);
if (!pMem)
return ERROR_NOT_ENOUGH_MEMORY;
// Where is this page hiding?
DWORD dwOffs = dwPage * m_dwPageSize;
DWORD dwRead = 0;
#ifdef A51_STAGE_BELOW_INDEX
long lRes = m_pFile->Read(dwOffs, pMem, m_dwPageSize, &dwRead);
if (lRes != ERROR_SUCCESS)
{
_BtrMemFree(pMem);
return lRes;
}
#else
dwRes = SetFilePointer(m_hFile, dwOffs, 0, FILE_BEGIN);
if (dwRes != dwOffs)
{
_BtrMemFree(pMem);
return GetLastError();
}
// Too late; the page cannot escape us
BOOL bRes = ReadFile(m_hFile, pMem, m_dwPageSize, &dwRead, 0);
if (!bRes || dwRead != m_dwPageSize)
{
_BtrMemFree(pMem);
return GetLastError();
}
#endif
// Maybe this is a bogus page trying to pretend it is real
// Check the signature
*pPage = pMem;
return NO_ERROR;
}
//***************************************************************************
//
// CPageSource::PutPage
//
// Always rewrites; the file extent was grown when the page was allocated
// with NewPage, so the page already exists and the write should not fail
//
//***************************************************************************
// ok
DWORD CPageSource::PutPage(
LPVOID pPage,
DWORD dwType
)
{
// Force the page to confess its identity
DWORD *pdwHeader = LPDWORD(pPage);
DWORD dwPageId = pdwHeader[OFFSET_PAGE_ID];
pdwHeader[OFFSET_PAGE_TYPE] = dwType;
// Where is it on the disk, we ask ourselves
DWORD dwOffset = m_dwPageSize * dwPageId;
DWORD dwWritten = 0;
#ifdef A51_STAGE_BELOW_INDEX
long lRes = m_pFile->Write(dwOffset, pPage, m_dwPageSize, &dwWritten);
if(lRes != ERROR_SUCCESS)
return lRes;
#else
DWORD dwRes = SetFilePointer(m_hFile, dwOffset, 0, FILE_BEGIN);
if (dwRes != dwOffset)
return GetLastError();
// Commit the page to the disk
BOOL bRes = WriteFile(m_hFile, pPage, m_dwPageSize, &dwWritten, 0);
if (bRes == FALSE)
return GetLastError();
#endif
// We're happy. But wait! Have we flushed this thing lately?
// It would be a shame if the file buffers were much more enlightened
// about the future than the disk itself.
// ===================================================================
DWORD dwNow = GetCurrentTime();
if (dwNow - m_dwLastFlushTime > MAX_FLUSH_INTERVAL)
{
Flush();
m_dwLastFlushTime = dwNow;
}
return NO_ERROR;
}
//***************************************************************************
//
// CPageSource::NewPage
//
// Allocates a new page, preferring the free list
//
//***************************************************************************
// ok
DWORD CPageSource::NewPage(LPVOID *pRetPage)
{
DWORD dwRes;
if (pRetPage == 0)
return ERROR_INVALID_PARAMETER;
*pRetPage = 0;
// Try to reuse a page from the "free list". Fast, fun, efficient.
// ================================================================
if (m_dwNextFreePage)
{
LPDWORD pPage = 0;
DWORD dwPage = m_dwNextFreePage;
dwRes = GetPage(m_dwNextFreePage, (LPVOID *) &pPage);
if (dwRes)
return dwRes;
m_dwNextFreePage = pPage[OFFSET_NEXT_PAGE];
dwRes = WriteAdminPage(); // Update free list
if (dwRes)
return dwRes;
memset(pPage, 0, m_dwPageSize);
pPage[OFFSET_PAGE_ID] = dwPage;
*pRetPage = pPage;
return NO_ERROR;
}
// Well, I guess there weren't any free pages left. Oh, well. Make a new one.
// Move to the end of the file
// ============================================================================
DWORD dwCurrentExtent = m_dwTotalPages * m_dwPageSize;
DWORD dwNewExtent = dwCurrentExtent + m_dwPageSize;
dwRes = SetFilePointer(m_hFile, dwNewExtent, 0, FILE_BEGIN);
if (dwRes != dwNewExtent)
{
// Must be out of disk space. We're unhappy.
return GetLastError();
}
// Ahh... How are little file has grown. Now manufacture some
// nonsensical memory with nothing in it and assign the page number & signatures.
// ==============================================================================
LPDWORD pNewPage = (LPDWORD) _BtrMemAlloc(m_dwPageSize);
if (pNewPage == 0)
return ERROR_NOT_ENOUGH_MEMORY;
memset(pNewPage, 0, m_dwPageSize);
pNewPage[OFFSET_PAGE_ID] = m_dwTotalPages++;
*pRetPage = pNewPage;
return WriteAdminPage();
}
//***************************************************************************
//
// CPageSource::FreePage
//
// Called to delete or free a page. If the last page is the one
// being freed, then the file is truncated.
//
//***************************************************************************
// ok
DWORD CPageSource::FreePage(
LPVOID pPage
)
{
BOOL bRes;
DWORD dwRes;
LPDWORD pCast = LPDWORD(pPage);
DWORD dwPageId = pCast[OFFSET_PAGE_ID];
if (dwPageId == 0 || m_dwLogicalRoot == dwPageId)
{
DebugBreak();
// Oops. Tried to delete the admin page or active root
return ERROR_INVALID_PARAMETER;
}
// See if page is the last one. If so, simply truncate the
// file and don't add to the free list.
// ========================================================
#ifdef DONT_DO_THIS_ANY_MORE
if (dwPageId == m_dwTotalPages - 1)
{
m_dwTotalPages--;
DWORD dwTarget = dwPageId * m_dwPageSize;
dwRes = SetFilePointer(m_hFile, dwTarget, 0, FILE_BEGIN);
if (dwRes != dwTarget)
return GetLastError();
bRes = SetEndOfFile(m_hFile);
if (!bRes)
return GetLastError();
return WriteAdminPage();
}
#endif
// Otherwise, it goes onto the good ol' free list.
// ===============================================
pCast[OFFSET_NEXT_PAGE] = m_dwNextFreePage;
m_dwNextFreePage = dwPageId;
dwRes = PutPage(pPage, PAGE_TYPE_DELETED);
if (dwRes)
return dwRes;
dwRes = WriteAdminPage();
return dwRes;
}
//***************************************************************************
//
// CPageSource::FreePage
//
//***************************************************************************
//
DWORD CPageSource::FreePage(
DWORD dwId
)
{
LPDWORD pFakePage = (LPDWORD) _BtrMemAlloc(m_dwPageSize);
if (pFakePage == 0)
return ERROR_NOT_ENOUGH_MEMORY;
pFakePage[OFFSET_PAGE_TYPE] = PAGE_TYPE_ACTIVE;
pFakePage[OFFSET_PAGE_ID] = dwId;
DWORD dwRes = FreePage(pFakePage);
_BtrMemFree(pFakePage);
return dwRes;
}
//***************************************************************************
//
// SIdxKeyTable::GetRequiredPageMemory
//
// Returns the amount of memory required to store this object in a
// linear page
//
//***************************************************************************
// ok
DWORD SIdxKeyTable::GetRequiredPageMemory()
{
DWORD dwTotal = m_pStrPool->GetRequiredPageMemory();
// Size of the key lookup table & its sizing DWORD, and
// add in the child page & user data
dwTotal += sizeof(DWORD) + sizeof(WORD) * m_dwNumKeys;
dwTotal += sizeof(DWORD) + sizeof(DWORD) * m_dwNumKeys; // User data
dwTotal += sizeof(DWORD) + sizeof(DWORD) * (m_dwNumKeys+1); // Child pages
// Add in the key encoding table
dwTotal += sizeof(WORD) + sizeof(WORD) * m_dwKeyCodesUsed;
// Add in per page overhead
//
// Signature, Page Id, Next Page, Parent Page
dwTotal += sizeof(DWORD) * 4;
// (NOTE A): Add some safety margin...
dwTotal += sizeof(DWORD) * 2;
return dwTotal;
}
//***************************************************************************
//
// SIdxKeyTable::StealKeyFromSibling
//
// Transfers a key from the sibling via the parent in a sort of rotation:
//
// 10
// 1 2 12 13 14
//
// Where <this> is node (1,2) and sibling is (12,13). A single rotation
// moves 10 into (1,2) and grabs 12 from the sibling to replace it,
//
// 12
// 1 2 10 13 14
//
// We repeat this until minimum load of <this> is above const_MinimumLoad.
//
//***************************************************************************
// ok
DWORD SIdxKeyTable::StealKeyFromSibling(
SIdxKeyTable *pParent,
SIdxKeyTable *pSibling
)
{
DWORD dwData, dwChild;
WORD wID;
LPSTR pszKey = 0;
DWORD dwSiblingId = pSibling->GetPageId();
DWORD dwThisId = GetPageId();
for (WORD i = 0; i < WORD(pParent->GetNumKeys()); i++)
{
DWORD dwChildA = pParent->GetChildPage(i);
DWORD dwChildB = pParent->GetChildPage(i+1);
if (dwChildA == dwThisId && dwChildB == dwSiblingId)
{
pParent->GetKeyAt(i, &pszKey);
dwData = pParent->GetUserData(i);
FindKey(pszKey, &wID);
AddKey(pszKey, wID, dwData);
pParent->RemoveKey(i);
_BtrMemFree(pszKey);
pSibling->GetKeyAt(0, &pszKey);
dwData = pSibling->GetUserData(0);
dwChild = pSibling->GetChildPage(0);
pSibling->RemoveKey(0);
SetChildPage(wID+1, dwChild);
pParent->AddKey(pszKey, i, dwData);
pParent->SetChildPage(i, dwThisId);
pParent->SetChildPage(i+1, dwSiblingId);
_BtrMemFree(pszKey);
break;
}
else if (dwChildA == dwSiblingId && dwChildB == dwThisId)
{
pParent->GetKeyAt(i, &pszKey);
dwData = pParent->GetUserData(i);
FindKey(pszKey, &wID);
AddKey(pszKey, wID, dwData);
pParent->RemoveKey(i);
_BtrMemFree(pszKey);
WORD wSibId = (WORD) pSibling->GetNumKeys() - 1;
pSibling->GetKeyAt(wSibId, &pszKey);
dwData = pSibling->GetUserData(wSibId);
dwChild = pSibling->GetChildPage(wSibId+1);
pSibling->RemoveKey(wSibId);
SetChildPage(wID, dwChild);
pParent->AddKey(pszKey, i, dwData);
pParent->SetChildPage(i, dwSiblingId);
pParent->SetChildPage(i+1, dwThisId);
_BtrMemFree(pszKey);
break;
}
}
return NO_ERROR;
}
//***************************************************************************
//
// SIdxKeyTable::Collapse
//
// Collapses the contents of a node and its sibling into just one
// node and adjusts the parent.
//
// Precondition: The two siblings can be successfully collapsed
// into a single node, accomodate a key migrated from the parent
// and still safely fit into a single node. Page sizes are not
// checked here.
//
//***************************************************************************
// ok
DWORD SIdxKeyTable::Collapse(
SIdxKeyTable *pParent,
SIdxKeyTable *pDoomedSibling
)
{
WORD wId;
DWORD dwRes;
LPSTR pszKey = 0;
DWORD dwData;
DWORD dwChild;
BOOL bExtra;
DWORD dwSiblingId = pDoomedSibling->GetPageId();
DWORD dwThisId = GetPageId();
// Locate the node in the parent which points to the two
// siblings. Since we don't know which sibling this is,
// we have to take into account the two possibilites.
// Is <this> the right side or the left?
//
// 10 20 30 40
// | | | | |
// x Sib This x x
//
// vs.
// 10 20 30 40
// | | | | |
// x This Sib x x
//
// We then migrate the key down into the current node
// and remove it from the parent. We steal the first
//
// ======================================================
for (WORD i = 0; i < WORD(pParent->GetNumKeys()); i++)
{
DWORD dwChildA = pParent->GetChildPage(i);
DWORD dwChildB = pParent->GetChildPage(i+1);
if (dwChildA == dwSiblingId && dwChildB == dwThisId)
{
pParent->GetKeyAt(i, &pszKey);
dwData = pParent->GetUserData(i);
pParent->RemoveKey(i);
pParent->SetChildPage(i, dwThisId);
dwChild = pDoomedSibling->GetLastChildPage();
AddKey(pszKey, 0, dwData);
SetChildPage(0, dwChild);
_BtrMemFree(pszKey);
bExtra = FALSE;
break;
}
else if (dwChildA == dwThisId && dwChildB == dwSiblingId)
{
pParent->GetKeyAt(i, &pszKey);
dwData = pParent->GetUserData(i);
pParent->RemoveKey(i);
pParent->SetChildPage(i, dwThisId);
FindKey(pszKey, &wId);
AddKey(pszKey, wId, dwData);
_BtrMemFree(pszKey);
bExtra = TRUE;
break;
}
}
// Move all info from sibling into the current node.
// ==================================================
DWORD dwNumSibKeys = pDoomedSibling->GetNumKeys();
for (WORD i = 0; i < WORD(dwNumSibKeys); i++)
{
LPSTR pKeyStr = 0;
dwRes = pDoomedSibling->GetKeyAt(i, &pKeyStr);
if (dwRes)
return dwRes;
DWORD dwUserData = pDoomedSibling->GetUserData(i);
dwRes = FindKey(pKeyStr, &wId);
if (dwRes != ERROR_NOT_FOUND)
{
_BtrMemFree(pKeyStr);
return ERROR_BAD_FORMAT;
}
dwRes = AddKey(pKeyStr, wId, dwUserData);
dwChild = pDoomedSibling->GetChildPage(i);
SetChildPage(wId, dwChild);
_BtrMemFree(pKeyStr);
}
if (bExtra)
SetChildPage(WORD(GetNumKeys()), pDoomedSibling->GetLastChildPage());
pDoomedSibling->ZapPage();
return NO_ERROR;
}
//***************************************************************************
//
// SIdxKeyTable::GetRightSiblingOf
// SIdxKeyTable::GetRightSiblingOf
//
// Searches the child page pointers and returns the sibling of the
// specified page. A return value of zero indicates there was not
// sibling of the specified value in the direction requested.
//
//***************************************************************************
//
DWORD SIdxKeyTable::GetRightSiblingOf(
DWORD dwId
)
{
for (DWORD i = 0; i < m_dwNumKeys; i++)
{
if (m_pdwChildPageMap[i] == dwId)
return m_pdwChildPageMap[i+1];
}
return 0;
}
DWORD SIdxKeyTable::GetLeftSiblingOf(
DWORD dwId
)
{
for (DWORD i = 1; i < m_dwNumKeys+1; i++)
{
if (m_pdwChildPageMap[i] == dwId)
return m_pdwChildPageMap[i-1];
}
return 0;
}
//***************************************************************************
//
// SIdxKeyTable::Redist
//
// Used when inserting and performing a node split.
// Precondition:
// (a) The current node is oversized
// (b) <pParent> is ready to receive the new median key
// (c) <pNewSibling> is completely empty and refers to the lesser node (left)
// (d) All pages have assigned numbers
//
// We move the nodes from <this> into the <pNewSibling> until both
// are approximately half full. The median key is moved into the parent.
// May fail if <pNewSibling> cannot allocate memory for the new stuff.
//
// If any errors occur, the entire sequence should be considered as failed
// and the pages invalid.
//
//***************************************************************************
//
DWORD SIdxKeyTable::Redist(
SIdxKeyTable *pParent,
SIdxKeyTable *pNewSibling
)
{
DWORD dwRes;
WORD wID;
if (pParent == 0 || pNewSibling == 0)
return ERROR_INVALID_PARAMETER;
if (m_dwNumKeys < 3)
{
return ERROR_INVALID_DATA;
}
// Find median key info and put it into parent.
DWORD dwToTransfer = m_dwNumKeys / 2;
while (dwToTransfer--)
{
// Get 0th key
LPSTR pStr = 0;
dwRes = GetKeyAt(0, &pStr);
if (dwRes)
return dwRes;
DWORD dwUserData = GetUserData(0);
// Move stuff into younger sibling
dwRes = pNewSibling->FindKey(pStr, &wID);
if (dwRes != ERROR_NOT_FOUND)
{
_BtrMemFree(pStr);
return dwRes;
}
dwRes = pNewSibling->AddKey(pStr, wID, dwUserData);
_BtrMemFree(pStr);
if (dwRes)
return dwRes;
DWORD dwChildPage = GetChildPage(0);
pNewSibling->SetChildPage(wID, dwChildPage);
RemoveKey(0);
}
pNewSibling->SetChildPage(WORD(pNewSibling->GetNumKeys()), GetChildPage(0));
// Next key is the median key, which migrates to the parent.
LPSTR pStr = 0;
dwRes = GetKeyAt(0, &pStr);
if (dwRes)
return dwRes;
DWORD dwUserData = GetUserData(0);
dwRes = pParent->FindKey(pStr, &wID);
if (dwRes != ERROR_NOT_FOUND)
{
_BtrMemFree(pStr);
return dwRes;
}
dwRes = pParent->AddKey(pStr, wID, dwUserData);
_BtrMemFree(pStr);
if (dwRes)
return dwRes;
RemoveKey(0);
// Patch in the various page pointers
pParent->SetChildPage(wID, pNewSibling->GetPageId());
pParent->SetChildPage(wID+1, GetPageId());
// Everything else is already okay
return NO_ERROR;
}
//***************************************************************************
//
// SIdxKeyTable::SIdxKeyTable
//
//***************************************************************************
// ok
SIdxKeyTable::SIdxKeyTable()
{
m_dwRefCount = 0;
m_dwPageId = 0;
m_dwParentPageId = 0;
m_dwNumKeys = 0; // Num keys
m_pwKeyLookup = 0; // Offset of key into key-lookup-table
m_dwKeyLookupTotalSize = 0; // Elements in array
m_pwKeyCodes = 0; // Key encoding table
m_dwKeyCodesTotalSize = 0; // Total elements in array
m_dwKeyCodesUsed = 0; // Elements used
m_pStrPool = 0; // The pool associated with this key table
m_pdwUserData = 0; // Stores user DWORDs for each key
m_pdwChildPageMap = 0; // Stores the child page map (num keys + 1)
}
//***************************************************************************
//
//***************************************************************************
//
DWORD SIdxKeyTable::Clone(
OUT SIdxKeyTable **pRetCopy
)
{
SIdxKeyTable *pCopy = new SIdxKeyTable;
if (!pCopy)
return ERROR_NOT_ENOUGH_MEMORY;
pCopy->m_dwRefCount = 1;
pCopy->m_dwPageId = m_dwPageId;
pCopy->m_dwParentPageId = m_dwParentPageId;
pCopy->m_dwNumKeys = m_dwNumKeys;
pCopy->m_pwKeyLookup = (WORD *)_BtrMemAlloc(sizeof(WORD) * m_dwKeyLookupTotalSize);
if (pCopy->m_pwKeyLookup == 0)
return ERROR_NOT_ENOUGH_MEMORY;
memcpy(pCopy->m_pwKeyLookup, m_pwKeyLookup, sizeof(WORD) * m_dwKeyLookupTotalSize);
pCopy->m_dwKeyLookupTotalSize = m_dwKeyLookupTotalSize;
pCopy->m_pdwUserData = (DWORD *)_BtrMemAlloc(sizeof(DWORD) * m_dwKeyLookupTotalSize);
if (pCopy->m_pdwUserData == 0)
return ERROR_NOT_ENOUGH_MEMORY;
memcpy(pCopy->m_pdwUserData, m_pdwUserData, sizeof(DWORD) * m_dwKeyLookupTotalSize);
pCopy->m_pdwChildPageMap = (DWORD *) _BtrMemAlloc(sizeof(DWORD) * (m_dwKeyLookupTotalSize+1));
if (pCopy->m_pdwChildPageMap == 0)
return ERROR_NOT_ENOUGH_MEMORY;
memcpy(pCopy->m_pdwChildPageMap, m_pdwChildPageMap, sizeof(DWORD) * (m_dwKeyLookupTotalSize+1));
pCopy->m_dwKeyCodesTotalSize = m_dwKeyCodesTotalSize;
pCopy->m_pwKeyCodes = (WORD *) _BtrMemAlloc(sizeof(WORD) * m_dwKeyCodesTotalSize);
if (pCopy->m_pwKeyCodes == 0)
return ERROR_NOT_ENOUGH_MEMORY;
memcpy(pCopy->m_pwKeyCodes, m_pwKeyCodes, sizeof(WORD)* m_dwKeyCodesTotalSize);
pCopy->m_dwKeyCodesUsed = m_dwKeyCodesUsed;
m_pStrPool->Clone(&pCopy->m_pStrPool);
*pRetCopy = pCopy;
return NO_ERROR;
}
//***************************************************************************
//
// SIdxKeyTable::~SIdxKeyTable
//
//***************************************************************************
//
SIdxKeyTable::~SIdxKeyTable()
{
if (m_pwKeyCodes)
_BtrMemFree(m_pwKeyCodes);
if (m_pwKeyLookup)
_BtrMemFree(m_pwKeyLookup);
if (m_pdwUserData)
_BtrMemFree(m_pdwUserData);
if (m_pdwChildPageMap)
_BtrMemFree(m_pdwChildPageMap);
if (m_pStrPool)
delete m_pStrPool;
}
//***************************************************************************
//
// SIdxKeyTable::GetKeyAt
//
// Precondition: <wID> is correct
// The only real case of failure is that the return string cannot be allocated.
//
// Return values:
// NO_ERROR
// ERROR_NOT_ENOUGH_MEMORY
// ERROR_INVALID_PARAMETER
//
//***************************************************************************
// tested
DWORD SIdxKeyTable::GetKeyAt(
WORD wID,
LPSTR *pszKey
)
{
if (wID >= m_dwNumKeys || pszKey == 0)
return ERROR_INVALID_PARAMETER;
WORD wStartOffs = m_pwKeyLookup[wID];
WORD wNumTokens = m_pwKeyCodes[wStartOffs];
LPSTR Strings[MAX_TOKENS_PER_KEY];
DWORD dwTotalLengths = 0;
for (DWORD i = 0; i < DWORD(wNumTokens); i++)
{
Strings[i] = m_pStrPool->GetStrById(m_pwKeyCodes[wStartOffs+1+i]);
dwTotalLengths += strlen(Strings[i]);
}
LPSTR pszFinalStr = (LPSTR) _BtrMemAlloc(dwTotalLengths + 1 + wNumTokens);
if (!pszFinalStr)
return ERROR_NOT_ENOUGH_MEMORY;
*pszFinalStr = 0;
for (DWORD i = 0; i < DWORD(wNumTokens); i++)
{
if (i > 0)
strcat(pszFinalStr, "\\");
strcat(pszFinalStr, Strings[i]);
}
*pszKey = pszFinalStr;
return NO_ERROR;
}
//***************************************************************************
//
// SIdxStringPool::FindStr
//
// Finds a string in the pool, if present and returns the assigned
// offset. Uses a binary search.
//
// Return codes:
// NO_ERROR The string was found
// ERROR_NOT_FOND
//
//***************************************************************************
// tested
DWORD SIdxStringPool::FindStr(
IN LPSTR pszSearchKey,
OUT WORD *pwStringNumber,
OUT WORD *pwPoolOffset
)
{
if (m_dwNumStrings == 0)
{
*pwStringNumber = 0;
return ERROR_NOT_FOUND;
}
// Binary search current node for key match.
// =========================================
int nPosition = 0;
int l = 0, u = int(m_dwNumStrings) - 1;
while (l <= u)
{
int m = (l + u) / 2;
// m is the current key to consider 0...n-1
LPSTR pszCandidateKeyStr = m_pStringPool+m_pwOffsets[m];
int nRes = strcmp(pszSearchKey, pszCandidateKeyStr);
// Decide which way to cut the array in half.
// ==========================================
if (nRes < 0)
{
u = m - 1;
nPosition = u + 1;
}
else if (nRes > 0)
{
l = m + 1;
nPosition = l;
}
else
{
// If here, we found the darn thing. Life is good.
// Populate the key unit.
// ================================================
if (pwStringNumber)
*pwStringNumber = WORD(m);
if (pwPoolOffset)
*pwPoolOffset = m_pwOffsets[m];
return NO_ERROR;
}
}
// Not found, if here. We record where the key should have been
// and tell the user the unhappy news.
// ==============================================================
*pwStringNumber = WORD(short(nPosition)); // The key would have been 'here'
return ERROR_NOT_FOUND;
}
//***************************************************************************
//
//***************************************************************************
//
DWORD SIdxStringPool::Clone(
SIdxStringPool **pRetCopy
)
{
SIdxStringPool *pCopy = new SIdxStringPool;
if (pCopy == 0)
return ERROR_NOT_ENOUGH_MEMORY;
pCopy->m_dwNumStrings = m_dwNumStrings;
pCopy->m_pwOffsets = (WORD *) _BtrMemAlloc(sizeof(WORD)*m_dwOffsetsSize);
if (pCopy->m_pwOffsets == 0)
return ERROR_NOT_ENOUGH_MEMORY;
memcpy(pCopy->m_pwOffsets, m_pwOffsets, sizeof(WORD)*m_dwOffsetsSize);
pCopy->m_dwOffsetsSize = m_dwOffsetsSize;
pCopy->m_pStringPool = (LPSTR) _BtrMemAlloc(m_dwPoolTotalSize);
if (pCopy->m_pStringPool == 0)
return ERROR_NOT_ENOUGH_MEMORY;
memcpy(pCopy->m_pStringPool, m_pStringPool, m_dwPoolTotalSize);
pCopy->m_dwPoolTotalSize = m_dwPoolTotalSize;
pCopy->m_dwPoolUsed = m_dwPoolUsed;
*pRetCopy = pCopy;
return NO_ERROR;
}
//***************************************************************************
//
// SIdxStringPool::DeleteStr
//
// Removes a string from the pool and pool index.
// Precondition: <wStringNum> is known to be valid by virtue of a prior
// call to <FindStr>.
//
// Return values:
// NO_ERROR <Cannot fail if precondition is met>.
//
//***************************************************************************
//
DWORD SIdxStringPool::DeleteStr(
WORD wStringNum,
int *pnAdjuster
)
{
if (pnAdjuster)
*pnAdjuster = 0;
// Find the address of the string to be removed.
// =============================================
DWORD dwTargetOffs = m_pwOffsets[wStringNum];
LPSTR pszDoomed = m_pStringPool+dwTargetOffs;
DWORD dwDoomedStrLen = strlen(pszDoomed) + 1;
// Copy all subsequent strings over the top and shorten the heap.
// Special case if this already the last string
// ==============================================================
DWORD dwStrBytesToMove = DWORD(m_pStringPool+m_dwPoolUsed - pszDoomed - dwDoomedStrLen);
if (dwStrBytesToMove)
memmove(pszDoomed, pszDoomed+dwDoomedStrLen, dwStrBytesToMove);
m_dwPoolUsed -= dwDoomedStrLen;
// Remove this entry from the array.
// =================================
DWORD dwArrayElsToMove = m_dwNumStrings - wStringNum - 1;
if (dwArrayElsToMove)
{
memmove(m_pwOffsets+wStringNum, m_pwOffsets+wStringNum+1, dwArrayElsToMove * sizeof(WORD));
if (pnAdjuster)
*pnAdjuster = -1;
}
m_dwNumStrings--;
// For all remaining elements, adjust offsets that were affected.
// ==============================================================
for (DWORD dwTrace = 0; dwTrace < m_dwNumStrings; dwTrace++)
{
if (m_pwOffsets[dwTrace] > dwTargetOffs)
m_pwOffsets[dwTrace] -= WORD(dwDoomedStrLen);
}
// Adjust sizes.
// =============
return NO_ERROR;
}
//***************************************************************************
//
// SIdxStringPool::AddStr
//
// Adds a string to the pool. Assumes it is known prior to the call that
// the string isn't present.
//
// Parameters:
// pszString The string to add
// pwAssignedOffset Returns the offset code assigned to the string
// Return values:
// NO_ERROR
// ERROR_NOT_ENOUGH_MEMORY
//
//***************************************************************************
// ok
DWORD SIdxStringPool::AddStr(
LPSTR pszString,
WORD wInsertPos,
int *pnAdjuster
)
{
if (pnAdjuster)
*pnAdjuster = 0;
// Precondition: String doesn't exist in the table
// Determine if the pool is too small for another string.
// If so, extend it.
// ======================================================
DWORD dwRequired = strlen(pszString)+1;
DWORD dwPoolFree = m_dwPoolTotalSize - m_dwPoolUsed;
if (m_dwPoolUsed + dwRequired - 1 > MAX_WORD_VALUE)
{
return ERROR_INSUFFICIENT_BUFFER;
}
if (dwRequired > dwPoolFree)
{
// Try to grow the pool
// ====================
LPVOID pTemp = _BtrMemReAlloc(m_pStringPool, m_dwPoolTotalSize * 2);
if (!pTemp) {
return ERROR_NOT_ENOUGH_MEMORY;
}
m_dwPoolTotalSize *= 2;
m_pStringPool = (LPSTR) pTemp;
}
// If array too small, reallocate to larger one
// ============================================
if (m_dwNumStrings == m_dwOffsetsSize)
{
// Realloc; double current size
LPVOID pTemp = _BtrMemReAlloc(m_pwOffsets, m_dwOffsetsSize * sizeof(WORD) * 2);
if (!pTemp)
return ERROR_NOT_ENOUGH_MEMORY;
m_dwOffsetsSize *= 2;
m_pwOffsets = PWORD(pTemp);
}
// If here, no problem. We have enough space for everything.
// =========================================================
LPSTR pszInsertAddr = m_pStringPool+m_dwPoolUsed;
DWORD dwInsertOffs = m_dwPoolUsed;
strcpy(pszInsertAddr, pszString);
m_dwPoolUsed += dwRequired;
// If here, there is enough room.
// ==============================
DWORD dwToBeMoved = m_dwNumStrings - wInsertPos;
if (dwToBeMoved)
{
memmove(&m_pwOffsets[wInsertPos+1], &m_pwOffsets[wInsertPos], sizeof(WORD)*dwToBeMoved);
if (pnAdjuster)
*pnAdjuster = 1;
}
m_pwOffsets[wInsertPos] = WORD(dwInsertOffs);
m_dwNumStrings++;
return NO_ERROR;
}
//***************************************************************************
//
// ParseIntoTokens
//
// Parses a slash separated string into separate tokens in preparation
// for encoding into the string pool. Call FreeStringArray on the output
// when no longer needed.
//
// No more than MAX_TOKEN_PER_KEY are supported. This means that
// if backslashes are used, no more than MAX_TOKEN_PER_KEY units can
// be parsed out.
//
// Returns:
// ERROR_INVALID_PARAMETER
// ERROR_NOT_ENOUGH_MEMORY
// NO_ERROR
//
//***************************************************************************
// ok
DWORD ParseIntoTokens(
IN LPSTR pszSource,
OUT DWORD *pdwTokenCount,
OUT LPSTR **pszTokens
)
{
LPSTR Strings[MAX_TOKENS_PER_KEY];
DWORD dwParseCount = 0, i = 0;
DWORD dwSourceLen = strlen(pszSource);
LPSTR *pszRetStr = 0;
DWORD dwRet;
if (pszSource == 0 || *pszSource == 0)
return ERROR_INVALID_PARAMETER;
LPSTR pszTempBuf = (LPSTR) _BtrMemAlloc(dwSourceLen+1);
if (!pszTempBuf)
return ERROR_NOT_ENOUGH_MEMORY;
LPSTR pszTracer = pszTempBuf;
for (;;)
{
*pszTracer = *pszSource;
if (*pszTracer == '\\' || *pszTracer == 0)
{
*pszTracer = 0; // Replace with null terminator
LPSTR pszTemp2 = (LPSTR) _BtrMemAlloc(strlen(pszTempBuf)+1);
if (pszTemp2 == 0)
{
dwRet = ERROR_NOT_ENOUGH_MEMORY;
goto Error;
}
if (dwParseCount == MAX_TOKENS_PER_KEY)
{
_BtrMemFree(pszTemp2);
dwRet = ERROR_INVALID_DATA;
goto Error;
}
strcpy(pszTemp2, pszTempBuf);
Strings[dwParseCount++] = pszTemp2;
pszTracer = pszTempBuf;
pszTracer--;
}
if (*pszSource == 0)
break;
pszTracer++;
pszSource++;
}
// If here, we at least parsed one string.
// =======================================
pszRetStr = (LPSTR *) _BtrMemAlloc(sizeof(LPSTR) * dwParseCount);
if (pszRetStr == 0)
{
dwRet = ERROR_NOT_ENOUGH_MEMORY;
goto Error;
}
memcpy(pszRetStr, Strings, sizeof(LPSTR) * dwParseCount);
*pdwTokenCount = dwParseCount;
*pszTokens = pszRetStr;
_BtrMemFree(pszTempBuf);
return NO_ERROR;
Error:
for (i = 0; i < dwParseCount; i++)
_BtrMemFree(Strings[i]);
*pdwTokenCount = 0;
_BtrMemFree(pszTempBuf);
return dwRet;
}
//***************************************************************************
//
// FreeTokenArray
//
// Cleans up the array returned by ParseIntoTokens.
//
//***************************************************************************
// ok
void FreeTokenArray(
DWORD dwCount,
LPSTR *pszStrings
)
{
for (DWORD i = 0; i < dwCount; i++)
_BtrMemFree(pszStrings[i]);
_BtrMemFree(pszStrings);
}
//***************************************************************************
//
// SIdxKeyTable::ZapPage
//
// Empties the page completely of all keys, codes, strings
//
//***************************************************************************
// ok
void SIdxKeyTable::ZapPage()
{
m_pStrPool->Empty();
m_dwKeyCodesUsed = 0;
m_dwNumKeys = 0;
}
//***************************************************************************
//
// SIdxKeyTable::MapFromPage
//
// CAUTION!!!
// The placement of DWORDs and WORDs is arranged to avoid 64-bit
// alignment faults.
//
//***************************************************************************
// ok
DWORD SIdxKeyTable::MapFromPage(LPVOID pSrc)
{
if (pSrc == 0)
return ERROR_INVALID_PARAMETER;
// Header
//
// DWORD[0] Signature
// DWORD[1] Page number
// DWORD[2] Next Page (always zero)
// ==================================\
LPDWORD pDWCast = (LPDWORD) pSrc;
if (*pDWCast++ != CPageSource::PAGE_TYPE_ACTIVE)
{
return ERROR_BAD_FORMAT;
}
m_dwPageId = *pDWCast++;
pDWCast++; // Skip the 'next page' field
// Key lookup table info
//
// DWORD[0] Parent Page
// DWORD[1] Num Keys = n
// DWORD[n] User Data
// DWORD[n+1] Child Page Map
// WORD[n] Key encoding offsets array
// ======================================
m_dwParentPageId = *pDWCast++;
m_dwNumKeys = *pDWCast++;
// Decide the allocation sizes and build the arrays
// ================================================
if (m_dwNumKeys <= const_DefaultArray)
m_dwKeyLookupTotalSize = const_DefaultArray;
else
m_dwKeyLookupTotalSize = m_dwNumKeys;
m_pdwUserData = (DWORD*) _BtrMemAlloc(m_dwKeyLookupTotalSize * sizeof(DWORD));
m_pdwChildPageMap = (DWORD*) _BtrMemAlloc((m_dwKeyLookupTotalSize+1) * sizeof(DWORD));
m_pwKeyLookup = (WORD*) _BtrMemAlloc(m_dwKeyLookupTotalSize * sizeof(WORD));
if (m_pdwUserData == 0 || m_pdwChildPageMap == 0 || m_pwKeyLookup == 0)
{
return ERROR_NOT_ENOUGH_MEMORY;
}
// Copy the page info into the arrays
// ==================================
memcpy(m_pdwUserData, pDWCast, sizeof(DWORD) * m_dwNumKeys);
pDWCast += m_dwNumKeys;
memcpy(m_pdwChildPageMap, pDWCast, sizeof(DWORD) * (m_dwNumKeys+1));
pDWCast += m_dwNumKeys + 1;
memcpy(m_pwKeyLookup, pDWCast, sizeof(WORD) * m_dwNumKeys);
LPWORD pWCast = LPWORD(pDWCast);
pWCast += m_dwNumKeys;
// Key encoding table info
//
// WORD[0] Num key codes = n
// WORD[n] Key codes
// ===========================
m_dwKeyCodesUsed = (DWORD) *pWCast++;
if (m_dwKeyCodesUsed <= const_DefaultKeyCodeArray)
m_dwKeyCodesTotalSize = const_DefaultKeyCodeArray;
else
m_dwKeyCodesTotalSize = m_dwKeyCodesUsed;
m_pwKeyCodes = (WORD*) _BtrMemAlloc(m_dwKeyCodesTotalSize * sizeof(WORD));
if (!m_pwKeyCodes)
{
return ERROR_NOT_ENOUGH_MEMORY;
}
memcpy(m_pwKeyCodes, pWCast, sizeof(WORD) * m_dwKeyCodesUsed);
pWCast += m_dwKeyCodesUsed;
// String pool
//
// WORD[0] Num strings = n
// WORD[n] Offsets
//
// WORD[0] String pool size = n
// BYTE[n] String pool
// =============================
m_pStrPool = new SIdxStringPool;
if (!m_pStrPool)
return ERROR_NOT_ENOUGH_MEMORY;
m_pStrPool->m_dwNumStrings = (DWORD) *pWCast++;
if (m_pStrPool->m_dwNumStrings <= const_DefaultArray)
m_pStrPool->m_dwOffsetsSize = const_DefaultArray;
else
m_pStrPool->m_dwOffsetsSize = m_pStrPool->m_dwNumStrings;
m_pStrPool->m_pwOffsets = (WORD *) _BtrMemAlloc(sizeof(WORD)* m_pStrPool->m_dwOffsetsSize);
if (m_pStrPool->m_pwOffsets == 0)
return ERROR_NOT_ENOUGH_MEMORY;
memcpy(m_pStrPool->m_pwOffsets, pWCast, sizeof(WORD)*m_pStrPool->m_dwNumStrings);
pWCast += m_pStrPool->m_dwNumStrings;
// String pool setup
// =================
m_pStrPool->m_dwPoolUsed = *pWCast++;
LPSTR pszCast = LPSTR(pWCast);
if (m_pStrPool->m_dwPoolUsed <= SIdxStringPool::const_DefaultPoolSize)
m_pStrPool->m_dwPoolTotalSize = SIdxStringPool::const_DefaultPoolSize;
else
m_pStrPool->m_dwPoolTotalSize = m_pStrPool->m_dwPoolUsed;
m_pStrPool->m_pStringPool = (LPSTR) _BtrMemAlloc(m_pStrPool->m_dwPoolTotalSize);
if (m_pStrPool->m_pStringPool == 0)
{
return ERROR_NOT_ENOUGH_MEMORY;
}
memcpy(m_pStrPool->m_pStringPool, pszCast, m_pStrPool->m_dwPoolUsed);
return NO_ERROR;
}
//***************************************************************************
//
// SIdxKeyTable::MapToPage
//
// Copies the info to a linear page. Precondition: the page must
// be large enough by validating using a prior test to GetRequiredPageMemory.
//
//***************************************************************************
// ok
DWORD SIdxKeyTable::MapToPage(LPVOID pDest)
{
if (pDest == 0)
return ERROR_INVALID_PARAMETER;
// Header
//
// DWORD[0] Signature
// DWORD[1] Page number
// DWORD[2] Next Page (always zero)
// ==================================\
LPDWORD pDWCast = (LPDWORD) pDest;
*pDWCast++ = CPageSource::PAGE_TYPE_ACTIVE;
*pDWCast++ = m_dwPageId;
*pDWCast++ = 0; // Unused 'next page' field
// Key lookup table info
//
// DWORD[0] Parent Page
// DWORD[1] Num Keys = n
// DWORD[n] User Data
// DWORD[n+1] Child Page Map
// WORD[n] Key encoding offsets array
// ======================================
*pDWCast++ = m_dwParentPageId;
*pDWCast++ = m_dwNumKeys;
// Decide the allocation sizes and build the arrays
// ================================================
memcpy(pDWCast, m_pdwUserData, sizeof(DWORD) * m_dwNumKeys);
pDWCast += m_dwNumKeys;
memcpy(pDWCast, m_pdwChildPageMap, sizeof(DWORD) * (m_dwNumKeys+1));
pDWCast += m_dwNumKeys + 1;
memcpy(pDWCast, m_pwKeyLookup, sizeof(WORD) * m_dwNumKeys);
LPWORD pWCast = LPWORD(pDWCast);
pWCast += m_dwNumKeys;
// Key encoding table info
//
// WORD[0] Num key codes = n
// WORD[n] Key codes
// ===========================
*pWCast++ = WORD(m_dwKeyCodesUsed);
memcpy(pWCast, m_pwKeyCodes, sizeof(WORD) * m_dwKeyCodesUsed);
pWCast += m_dwKeyCodesUsed;
// String pool
//
// WORD[0] Num strings = n
// WORD[n] Offsets
//
// WORD[0] String pool size = n
// BYTE[n] String pool
// =============================
*pWCast++ = WORD(m_pStrPool->m_dwNumStrings);
memcpy(pWCast, m_pStrPool->m_pwOffsets, sizeof(WORD)*m_pStrPool->m_dwNumStrings);
pWCast += m_pStrPool->m_dwNumStrings;
*pWCast++ = WORD(m_pStrPool->m_dwPoolUsed);
LPSTR pszCast = LPSTR(pWCast);
memcpy(pszCast, m_pStrPool->m_pStringPool, m_pStrPool->m_dwPoolUsed);
return NO_ERROR;
}
//***************************************************************************
//
// SIdxKeyTable::Create
//
// Does a default create
//
//***************************************************************************
// ok
DWORD SIdxKeyTable::Create(
DWORD dwPageId,
OUT SIdxKeyTable **pNewInst
)
{
SIdxKeyTable *p = new SIdxKeyTable;
if (!p)
return ERROR_NOT_ENOUGH_MEMORY;
// Set up default string pool, arrays, etc.
// ========================================
p->m_dwPageId = dwPageId;
p->m_dwNumKeys = 0;
p->m_pwKeyLookup = (WORD*) _BtrMemAlloc(const_DefaultArray * sizeof(WORD));
p->m_dwKeyLookupTotalSize = const_DefaultArray;
p->m_pwKeyCodes = (WORD*) _BtrMemAlloc(const_DefaultArray * sizeof(WORD));
p->m_dwKeyCodesTotalSize = const_DefaultArray;
p->m_dwKeyCodesUsed = 0;
p->m_pdwUserData = (DWORD*) _BtrMemAlloc(const_DefaultArray * sizeof(DWORD));
p->m_pdwChildPageMap = (DWORD*) _BtrMemAlloc((const_DefaultArray+1) * sizeof(DWORD));
// Set up string pool.
// ===================
p->m_pStrPool = new SIdxStringPool;
p->m_pStrPool->m_pwOffsets = (WORD*) _BtrMemAlloc(const_DefaultArray * sizeof(WORD));
p->m_pStrPool->m_dwOffsetsSize = const_DefaultArray;
p->m_pStrPool->m_pStringPool = (LPSTR) _BtrMemAlloc(SIdxStringPool::const_DefaultPoolSize);
p->m_pStrPool->m_dwPoolTotalSize = SIdxStringPool::const_DefaultPoolSize;
// Check all pointers. If any are null, error out.
// ================================================
if (
p->m_pwKeyLookup == NULL ||
p->m_pwKeyCodes == NULL ||
p->m_pdwUserData == NULL ||
p->m_pdwChildPageMap == NULL ||
p->m_pStrPool == NULL ||
p->m_pStrPool->m_pwOffsets == NULL ||
p->m_pStrPool->m_pStringPool == NULL
)
{
delete p;
*pNewInst = 0;
return ERROR_NOT_ENOUGH_MEMORY;
}
// Return good object to caller.
// =============================
p->AddRef();
*pNewInst = p;
return NO_ERROR;
}
//***************************************************************************
//
// SIdxStringPool::~SIdxStringPool
//
//***************************************************************************
// ok
SIdxStringPool::~SIdxStringPool()
{
if (m_pwOffsets)
_BtrMemFree(m_pwOffsets);
m_pwOffsets = 0;
if (m_pStringPool)
_BtrMemFree(m_pStringPool); // Pointer to string pool
m_pStringPool = 0;
}
//***************************************************************************
//
// SIdxKeyTable::Create
//
// Does a default create
//
//***************************************************************************
// ok
DWORD SIdxKeyTable::Create(
IN LPVOID pPage,
OUT SIdxKeyTable **pNewInst
)
{
SIdxKeyTable *p = new SIdxKeyTable;
if (!p)
return ERROR_NOT_ENOUGH_MEMORY;
DWORD dwRes = p->MapFromPage(pPage);
if (dwRes)
{
*pNewInst = 0;
return dwRes;
}
p->AddRef();
*pNewInst = p;
return NO_ERROR;
}
//***************************************************************************
//
// SIdxKeyTable::AddRef
//
//***************************************************************************
// ok
DWORD SIdxKeyTable::AddRef()
{
InterlockedIncrement((LONG *) &m_dwRefCount);
return m_dwRefCount;
}
//***************************************************************************
//
// SIdxKeyTable::Release
//
//***************************************************************************
// ok
DWORD SIdxKeyTable::Release()
{
DWORD dwNewCount = InterlockedDecrement((LONG *) &m_dwRefCount);
if (0 != dwNewCount)
return dwNewCount;
delete this;
return 0;
}
//***************************************************************************
//
// SIdxKeyTable::Cleanup
//
// Does a consistency check of the key encoding table and cleans up the
// string pool if any strings aren't being referenced.
//
//***************************************************************************
// ok
DWORD SIdxKeyTable::Cleanup()
{
// See if all string pool codes are used in key code table.
// If not, remove the string pool code.
// =======================================================
DWORD dwLastId = m_pStrPool->GetLastId();
BOOL *pCheck = (BOOL*) _BtrMemAlloc(sizeof(BOOL) * dwLastId);
if (!pCheck)
return ERROR_NOT_ENOUGH_MEMORY;
while (1)
{
if (m_pStrPool->GetNumStrings() == 0 || m_dwKeyCodesUsed == 0 || m_dwNumKeys == 0)
{
ZapPage();
break;
}
dwLastId = m_pStrPool->GetLastId();
memset(pCheck, 0, sizeof(BOOL)*dwLastId); // Mark all codes as 'unused'
// Move through all key codes. If we delete a key encoding, there
// may be a code in the string pool not used by the encoding.
// What we have to do is set the pCheck array to TRUE for each
// code encountered. If any have FALSE when we are done, we have
// an unused code.
WORD wCurrentSequence = 0;
for (DWORD i = 0; i < m_dwKeyCodesUsed; i++)
{
if (wCurrentSequence == 0) // Skip the length WORD
{
wCurrentSequence = m_pwKeyCodes[i];
continue;
}
else // A string pool code
pCheck[m_pwKeyCodes[i]] = TRUE;
wCurrentSequence--;
}
// Now the pCheck array contains deep within its psyche
// the knowledge of whether or not all string pool codes
// were used TRUE for referenced ones, FALSE for those
// not referenced. Let's look through it and see!
DWORD dwUsed = 0, dwUnused = 0;
for (i = 0; i < dwLastId; i++)
{
if (pCheck[i] == FALSE)
{
dwUnused++;
// Yikes! A lonely, unused string code. Let's be merciful
// and zap it before it knows the difference.
// =======================================================
int nAdj = 0;
m_pStrPool->DeleteStr(WORD(i), &nAdj);
AdjustKeyCodes(WORD(i), nAdj);
break;
}
else
dwUsed++;
}
if (dwUnused == 0)
break;
}
_BtrMemFree(pCheck);
return NO_ERROR;
}
//***************************************************************************
//
// SIdxKeyTable::AdjustKeyCodes
//
//***************************************************************************
// ok
void SIdxKeyTable::AdjustKeyCodes(
WORD wID,
int nAdjustment
)
{
// Adjust all key codes starting with wID by the amount of the
// adjustment, skipping length bytes.
// =============================================================
WORD wCurrentSequence = 0;
for (DWORD i = 0; i < m_dwKeyCodesUsed; i++)
{
if (wCurrentSequence == 0)
{
wCurrentSequence = m_pwKeyCodes[i];
continue;
}
else
{
if (m_pwKeyCodes[i] >= wID)
m_pwKeyCodes[i] = m_pwKeyCodes[i] + nAdjustment;
}
wCurrentSequence--;
}
}
//***************************************************************************
//
// SIdxKeyTable::AddKey
//
// Adds a string to the table at position <wID>. Assumes FindString
// was called first to get the correct location.
//
// Precondition: <pszStr> is valid, and <wID> is correct.
//
// Return codes:
//
// ERROR_OUT_OF_MEMORY
// NO_ERROR
// ERROR_INVALID_PARAMETER // Too many slashes in key
//
//***************************************************************************
// ok
DWORD SIdxKeyTable::AddKey(
LPSTR pszStr,
WORD wKeyID,
DWORD dwUserData
)
{
DWORD dwRes, dwRet;
LPVOID pTemp = 0;
LPSTR pszTemp = 0;
DWORD dwLen, i;
DWORD dwTokenCount = 0;
WORD *pwTokenIDs = 0;
DWORD dwNumNewTokens = 0;
LPSTR *pszStrings = 0;
DWORD dwToBeMoved;
DWORD dwStartingOffset;
// Set up some temp working arrays.
// ================================
if (!pszStr)
return ERROR_INVALID_PARAMETER;
dwLen = strlen(pszStr);
if (dwLen == 0)
return ERROR_INVALID_PARAMETER;
pszTemp = (LPSTR) _BtrMemAlloc(dwLen+1);
if (!pszTemp)
{
dwRet = ERROR_NOT_ENOUGH_MEMORY;
goto Exit;
}
// Ensure there is enough room.
// ============================
if (m_dwKeyLookupTotalSize == m_dwNumKeys)
{
// Expand the array.
DWORD dwNewSize = m_dwKeyLookupTotalSize * 2;
pTemp = _BtrMemReAlloc(m_pwKeyLookup, dwNewSize * sizeof(WORD));
if (!pTemp)
{
dwRet = ERROR_NOT_ENOUGH_MEMORY;
goto Exit;
}
m_dwKeyLookupTotalSize = dwNewSize;
m_pwKeyLookup = PWORD(pTemp);
// Expand user data.
pTemp = _BtrMemReAlloc(m_pdwUserData, dwNewSize * sizeof(DWORD));
if (!pTemp)
{
dwRet = ERROR_NOT_ENOUGH_MEMORY;
goto Exit;
}
m_pdwUserData = (DWORD *) pTemp;
// Expand child page map.
pTemp = _BtrMemReAlloc(m_pdwChildPageMap, (dwNewSize + 1) * sizeof(DWORD));
if (!pTemp)
{
dwRet = ERROR_NOT_ENOUGH_MEMORY;
goto Exit;
}
m_pdwChildPageMap = (DWORD *) pTemp;
}
// Parse the string into backslash separated tokens.
// =================================================
dwRes = ParseIntoTokens(pszStr, &dwTokenCount, &pszStrings);
if (dwRes)
{
dwRet = dwRes;
goto Exit;
}
// Allocate an array to hold the IDs of the tokens in the string.
// ==============================================================
pwTokenIDs = (WORD *) _BtrMemAlloc(sizeof(WORD) * dwTokenCount);
if (pwTokenIDs == 0)
{
dwRet = ERROR_NOT_ENOUGH_MEMORY;
goto Exit;
}
// Work through the tokens and add them to the pool & key encoding table.
// =============================================================
for (i = 0; i < dwTokenCount; i++)
{
LPSTR pszTok = pszStrings[i];
// See if token exists, if not add it.
// ===================================
WORD wID = 0;
dwRes = m_pStrPool->FindStr(pszTok, &wID, 0);
if (dwRes == NO_ERROR)
{
// Found it
pwTokenIDs[dwNumNewTokens++] = wID;
}
else if (dwRes == ERROR_NOT_FOUND)
{
int nAdjustment = 0;
dwRes = m_pStrPool->AddStr(pszTok, wID, &nAdjustment);
if (dwRes)
{
dwRet = dwRes;
goto Exit;
}
// Adjust string IDs because of the addition.
// All existing ones with the same ID or higher
// must be adjusted upwards.
if (nAdjustment)
{
AdjustKeyCodes(wID, nAdjustment);
for (DWORD i2 = 0; i2 < dwNumNewTokens; i2++)
{
if (pwTokenIDs[i2] >= wID)
pwTokenIDs[i2] = pwTokenIDs[i2] + nAdjustment;
}
}
// Adjust current tokens to accomodate new
pwTokenIDs[dwNumNewTokens++] = wID;
}
else
{
dwRet = dwRes;
goto Exit;
}
}
// Now we know the encodings. Add them to the key encoding table.
// First make sure that there is enough room in the table.
// ===============================================================
if (m_dwKeyCodesTotalSize - m_dwKeyCodesUsed < dwNumNewTokens + 1)
{
DWORD dwNewSize = m_dwKeyCodesTotalSize * 2 + dwNumNewTokens + 1;
PWORD pTemp = (PWORD) _BtrMemReAlloc(m_pwKeyCodes, dwNewSize * sizeof(WORD));
if (!pTemp)
{
dwRet = ERROR_NOT_ENOUGH_MEMORY;
goto Exit;
}
m_pwKeyCodes = pTemp;
m_dwKeyCodesTotalSize = dwNewSize;
}
dwStartingOffset = m_dwKeyCodesUsed;
m_pwKeyCodes[m_dwKeyCodesUsed++] = (WORD) dwNumNewTokens; // First WORD is count of tokens
for (i = 0; i < dwNumNewTokens; i++) // Encoded tokens
m_pwKeyCodes[m_dwKeyCodesUsed++] = pwTokenIDs[i];
// Now, add in the new key lookup by inserting it into the array.
// ==============================================================
dwToBeMoved = m_dwNumKeys - wKeyID;
if (dwToBeMoved)
{
memmove(&m_pwKeyLookup[wKeyID+1], &m_pwKeyLookup[wKeyID], sizeof(WORD)*dwToBeMoved);
memmove(&m_pdwUserData[wKeyID+1], &m_pdwUserData[wKeyID], sizeof(DWORD)*dwToBeMoved);
memmove(&m_pdwChildPageMap[wKeyID+1], &m_pdwChildPageMap[wKeyID], (sizeof(DWORD))*(dwToBeMoved+1));
}
m_pwKeyLookup[wKeyID] = (WORD) dwStartingOffset;
m_pdwUserData[wKeyID] = dwUserData;
m_dwNumKeys++;
dwRet = NO_ERROR;
// Cleanup code.
// =============
Exit:
if (pszTemp)
_BtrMemFree(pszTemp);
FreeTokenArray(dwTokenCount, pszStrings);
if (pwTokenIDs)
_BtrMemFree(pwTokenIDs);
return dwRet;
}
//***************************************************************************
//
// SIdxKeyTable::RemoveKey
//
// Precondition: <wID> is the valid target
//
//***************************************************************************
// ok
DWORD SIdxKeyTable::RemoveKey(
WORD wID
)
{
// Find the key code sequence and remove it.
// =========================================
WORD wKeyCodeStart = m_pwKeyLookup[wID];
DWORD dwToBeMoved = m_dwNumKeys - DWORD(wID) - 1;
if (dwToBeMoved)
{
memmove(&m_pwKeyLookup[wID], &m_pwKeyLookup[wID+1], sizeof(WORD)*dwToBeMoved);
memmove(&m_pdwUserData[wID], &m_pdwUserData[wID+1], sizeof(DWORD)*dwToBeMoved);
memmove(&m_pdwChildPageMap[wID], &m_pdwChildPageMap[wID+1], sizeof(DWORD)*(dwToBeMoved+1));
}
m_dwNumKeys--;
// Zap the key encoding table to remove references to this key.
// ============================================================
WORD wCount = m_pwKeyCodes[wKeyCodeStart]+1;
dwToBeMoved = m_dwKeyCodesUsed - (wKeyCodeStart + wCount);
if (dwToBeMoved)
memmove(&m_pwKeyCodes[wKeyCodeStart], &m_pwKeyCodes[wKeyCodeStart + wCount], sizeof(WORD)*dwToBeMoved);
m_dwKeyCodesUsed -= wCount;
// Adjust all zapped key codes referenced by key lookup table.
// ===========================================================
for (DWORD i = 0; i < m_dwNumKeys; i++)
{
if (m_pwKeyLookup[i] >= wKeyCodeStart)
m_pwKeyLookup[i] -= wCount;
}
// Now check the string pool & key encoding table for
// unreferenced strings thanks to the above tricks
// and clean up the mess left behind!!
// ==================================================
return Cleanup();
}
//***************************************************************************
//
// Compares the literal string in <pszSearchKey> against the encoded
// string at <nID>. Returns the same value as strcmp().
//
// This is done by decoding the compressed string, token by token, and
// comparing each character to that in the search string.
//
//***************************************************************************
// ok
int SIdxKeyTable::KeyStrCompare(
LPSTR pszSearchKey,
WORD wID
)
{
LPSTR pszTrace = pszSearchKey;
WORD dwEncodingOffs = m_pwKeyLookup[wID];
WORD wNumTokens = m_pwKeyCodes[dwEncodingOffs];
WORD wStrId = m_pwKeyCodes[++dwEncodingOffs];
LPSTR pszDecoded = m_pStrPool->GetStrById(wStrId);
wNumTokens--;
int nRes;
while (1)
{
int nTraceChar = *pszTrace++;
int nCodedChar = *pszDecoded++;
if (nCodedChar == 0 && wNumTokens)
{
pszDecoded = m_pStrPool->GetStrById(m_pwKeyCodes[++dwEncodingOffs]);
wNumTokens--;
nCodedChar = '\\';
}
nRes = nTraceChar - nCodedChar;
if (nRes || (nTraceChar == 0 && nCodedChar == 0))
return nRes;
}
// Identical strings
return 0;
}
//***************************************************************************
//
// SIdxKeyTable::FindKey
//
// Finds the key in the key table, if present. If not, returns
// ERROR_NOT_FOUND and <pID> set to the point where it would be if
// later inserted.
//
// Return values:
// ERROR_NOT_FOUND
// NO_ERROR
//
//***************************************************************************
// ready for test
DWORD SIdxKeyTable::FindKey(
LPSTR pszSearchKey,
WORD *pID
)
{
if (pszSearchKey == 0 || *pszSearchKey == 0 || pID == 0)
return ERROR_INVALID_PARAMETER;
// Binary search the key table.
// ============================
if (m_dwNumKeys == 0)
{
*pID = 0;
return ERROR_NOT_FOUND;
}
int nPosition = 0;
int l = 0, u = int(m_dwNumKeys) - 1;
while (l <= u)
{
int m = (l + u) / 2;
int nRes;
// m is the current key to consider 0...n-1
try
{
nRes = KeyStrCompare(pszSearchKey, WORD(m));
}
catch(...)
{
DebugBreak();
nRes = KeyStrCompare(pszSearchKey, WORD(m));
}
// Decide which way to cut the array in half.
// ==========================================
if (nRes < 0)
{
u = m - 1;
nPosition = u + 1;
}
else if (nRes > 0)
{
l = m + 1;
nPosition = l;
}
else
{
// If here, we found the darn thing. Life is good.
// Populate the key unit.
// ================================================
*pID = WORD(m);
return NO_ERROR;
}
}
// Not found, if here. We record where the key should have been
// and tell the user the unhappy news.
// ==============================================================
*pID = WORD(nPosition); // The key would have been 'here'
return ERROR_NOT_FOUND;
}
//***************************************************************************
//
//***************************************************************************
// untested
DWORD SIdxKeyTable::Dump(FILE *f, DWORD *pdwKeys)
{
fprintf(f, "\t|---Begin Key Table Dump---\n");
fprintf(f, "\t| m_dwPageId = %d (0x%X)\n", m_dwPageId, m_dwPageId);
fprintf(f, "\t| m_dwParentPageId = %d\n", m_dwParentPageId);
fprintf(f, "\t| m_dwNumKeys = %d\n", m_dwNumKeys);
fprintf(f, "\t| m_pwKeyLookup = 0x%p\n", m_pwKeyLookup);
fprintf(f, "\t| m_dwKeyLookupTotalSize = %d\n", m_dwKeyLookupTotalSize);
fprintf(f, "\t| m_pwKeyCodes = 0x%p\n", m_pwKeyCodes);
fprintf(f, "\t| m_dwKeyCodesTotalSize = %d\n", m_dwKeyCodesTotalSize);
fprintf(f, "\t| m_dwKeyCodesUsed = %d\n", m_dwKeyCodesUsed);
fprintf(f, "\t| Required Page Memory = %d\n", GetRequiredPageMemory());
fprintf(f, "\t| --Key Lookup Table\n");
if (pdwKeys)
*pdwKeys = m_dwNumKeys;
for (DWORD i = 0; i < m_dwNumKeys; i++)
{
fprintf(f, "\t| * Left Child Page ------------------------> %d\n", m_pdwChildPageMap[i]);
fprintf(f, "\t| KeyID[%d] = offset %d\n", i, m_pwKeyLookup[i]);
fprintf(f, "\t| User Data=%d\n", m_pdwUserData[i]);
WORD wKeyEncodingOffset = m_pwKeyLookup[i];
WORD wEncodingUnits = m_pwKeyCodes[wKeyEncodingOffset];
int nPass = 0;
fprintf(f, "\t | Key=");
for (DWORD i2 = 0; i2 < DWORD(wEncodingUnits); i2++)
{
WORD wCode = m_pwKeyCodes[wKeyEncodingOffset + 1 + i2];
if (nPass)
fprintf(f, "\\");
fprintf(f, "%s", m_pStrPool->GetStrById(wCode));
nPass++;
}
fprintf(f, "\n");
fprintf(f, "\t| Num encoding units = %d\n", wEncodingUnits);
for (DWORD i2 = 0; i2 < DWORD(wEncodingUnits); i2++)
{
WORD wCode = m_pwKeyCodes[wKeyEncodingOffset + 1 + i2];
fprintf(f, "\t | KeyCode = %d\n", wCode);
}
}
fprintf(f, "\t| * Rightmost child page -------------------> %d\n", m_pdwChildPageMap[i]);
fprintf(f, "\t|---\n");
#ifdef EXTENDED_STRING_TABLE_DUMP
fprintf(f, "\t|---Key Encoding Table\n");
WORD wCurrentSequence = 0;
for (i = 0; i < m_dwKeyCodesUsed; i++)
{
if (wCurrentSequence == 0)
{
wCurrentSequence = m_pwKeyCodes[i];
fprintf(f, "\t| KeyCode[%d] = %d <count>\n", i, m_pwKeyCodes[i]);
continue;
}
else
fprintf(f, "\t| KeyCode[%d] = %d <%s>\n", i, m_pwKeyCodes[i],
m_pStrPool->GetStrById(m_pwKeyCodes[i]));
wCurrentSequence--;
}
fprintf(f, "\t|---End Key Encoding Table---\n");
m_pStrPool->Dump(f);
#endif
return 0;
}
//***************************************************************************
//
// SIdxStringPool::Dump
//
// Dumps the string pool
//
//***************************************************************************
// tested
DWORD SIdxStringPool::Dump(FILE *f)
{
try
{
fprintf(f, "\t\t|| ---String Pool Dump---\n");
fprintf(f, "\t\t|| m_dwNumStrings = %d\n", m_dwNumStrings);
fprintf(f, "\t\t|| m_pwOffsets = 0x%p\n", m_pwOffsets);
fprintf(f, "\t\t|| m_dwOffsetsSize = %d\n", m_dwOffsetsSize);
fprintf(f, "\t\t|| m_pStringPool = 0x%p\n", m_pStringPool);
fprintf(f, "\t\t|| m_dwPoolTotalSize = %d\n", m_dwPoolTotalSize);
fprintf(f, "\t\t|| m_dwPoolUsed = %d\n", m_dwPoolUsed);
fprintf(f, "\t\t|| --Contents of offsets array--\n");
for (DWORD ix = 0; ix < m_dwNumStrings; ix++)
{
fprintf(f, "\t\t|| String[%d] = offset %d Value=<%s>\n",
ix, m_pwOffsets[ix], m_pStringPool+m_pwOffsets[ix]);
}
#ifdef EXTENDED_STRING_TABLE_DUMP
fprintf(f, "\t\t|| --String table--\n");
for (ix = 0; ix < m_dwPoolTotalSize; ix += 20)
{
fprintf(f, "\t\t || %4d ", ix);
for (int nSubcount = 0; nSubcount < 20; nSubcount++)
{
if (nSubcount + ix >= m_dwPoolTotalSize)
continue;
char c = m_pStringPool[ix+nSubcount];
fprintf(f, "%02x ", c);
}
for (int nSubcount = 0; nSubcount < 20; nSubcount++)
{
if (nSubcount + ix >= m_dwPoolTotalSize)
continue;
char c = m_pStringPool[ix+nSubcount];
if (c < 32)
{
c = '.';
}
fprintf(f, "%c ", c);
}
fprintf(f, "\n");
}
#endif
fprintf(f, "\t\t|| ---End of String Pool Dump\n");
}
catch(...)
{
printf("Exception during dump\n");
}
return 0;
}
//***************************************************************************
//
// CBTree::Init
//
//***************************************************************************
//
DWORD CBTree::Init(
CPageSource *pSrc
)
{
DWORD dwRes;
if (pSrc == 0)
return ERROR_INVALID_PARAMETER;
// Read the logical root page, if any. If the index is just
// being created, create the root index page.
m_pSrc = pSrc;
m_pRoot = 0;
DWORD dwRoot = m_pSrc->GetRootPage();
if (dwRoot == 0)
{
LPDWORD pNewPage = 0;
dwRes = m_pSrc->NewPage((LPVOID *) &pNewPage);
if (dwRes)
return dwRes;
DWORD dwPageNum = pNewPage[CPageSource::OFFSET_PAGE_ID];
_BtrMemFree(pNewPage);
dwRes = SIdxKeyTable::Create(dwPageNum, &m_pRoot);
if (dwRes)
return dwRes;
m_pSrc->SetRootPage(dwPageNum);
dwRes = WriteIdxPage(m_pRoot);
}
else
{
// Retrieve existing root
LPVOID pPage = 0;
dwRes = m_pSrc->GetPage(dwRoot, &pPage);
if (dwRes)
return dwRes;
dwRes = SIdxKeyTable::Create(pPage, &m_pRoot);
_BtrMemFree(pPage);
if (dwRes)
return dwRes;
}
return dwRes;
}
//***************************************************************************
//
// CBTree::CBTree
//
//***************************************************************************
//
CBTree::CBTree()
{
m_pSrc = 0;
m_pRoot = 0;
m_lGeneration = 0;
InitializeCriticalSection(&m_cs);
CBTreeIterator *m_pIterators;
DWORD m_dwActiveIterators;
DWORD m_dwIteratorsArraySize;
m_pIterators = 0;
m_dwActiveIterators = 0;
m_dwIteratorsArraySize = 0;
}
//***************************************************************************
//
// CBTree::~CBTree
//
//***************************************************************************
//
CBTree::~CBTree()
{
if (m_pSrc || m_pRoot)
{
Shutdown(WMIDB_SHUTDOWN_NET_STOP);
}
DeleteCriticalSection(&m_cs);
}
//***************************************************************************
//
// CBTree::Shutdown
//
//***************************************************************************
//
DWORD CBTree::Shutdown(DWORD dwShutDownFlags)
{
DWORD dwRes;
if (m_pRoot)
{
m_pRoot->Release();
m_pRoot = 0;
}
if (m_pSrc)
{
dwRes = m_pSrc->Shutdown(dwShutDownFlags);
m_pSrc = 0;
return dwRes;
}
return ERROR_INVALID_FUNCTION;
}
//***************************************************************************
//
// CBTree::InsertKey
//
// Inserts the key+data into the tree. Most of the work is done
// in InsertPhase2().
//
//***************************************************************************
// ok
DWORD CBTree::InsertKey(
IN LPSTR pszKey,
DWORD dwValue
)
{
WORD wID;
DWORD dwRes;
SIdxKeyTable *pIdx = 0;
LONG StackPtr = -1;
DWORD Stack[CBTreeIterator::const_MaxStack];
if (pszKey == 0 || *pszKey == 0)
return ERROR_INVALID_PARAMETER;
dwRes = Search(pszKey, &pIdx, &wID, Stack, StackPtr);
if (dwRes == 0)
{
// Ooops. Aleady exists. We can't insert it.
// ===========================================
pIdx->Release();
return ERROR_ALREADY_EXISTS;
}
if (dwRes != ERROR_NOT_FOUND)
return dwRes;
// If here, we can indeed add it.
// ==============================
dwRes = InsertPhase2(pIdx, wID, pszKey, dwValue, Stack, StackPtr);
pIdx->Release();
return dwRes;
}
//***************************************************************************
//
// CBTree::ComputeLoad
//
//***************************************************************************
//
DWORD CBTree::ComputeLoad(
SIdxKeyTable *pKT
)
{
DWORD dwMem = pKT->GetRequiredPageMemory();
DWORD dwLoad = dwMem * 100 / m_pSrc->GetPageSize();
return dwLoad;
}
//***************************************************************************
//
// CBTree::Search
//
// The actual search occurs here. Descends through the page mechanism.
//
// Returns:
// NO_ERROR <pPage> is assigned, and <pwID> points to the key.
//
// ERROR_NOT_FOUND <pPage> is assigned to where the insert should occur,
// at <pwID> in that page.
//
// Other errors don't assign the OUT parameters.
//
// Note: caller must release <pRetIdx> using Release() when it is returned
// whether with an error code or not.
//
//***************************************************************************
// ok
DWORD CBTree::Search(
IN LPSTR pszKey,
OUT SIdxKeyTable **pRetIdx,
OUT WORD *pwID,
DWORD Stack[],
LONG &StackPtr
)
{
DWORD dwRes, dwChildPage, dwPage;
if (pszKey == 0 || *pszKey == 0 || pwID == 0 || pRetIdx == 0)
return ERROR_INVALID_PARAMETER;
*pRetIdx = 0;
SIdxKeyTable *pIdx = m_pRoot;
pIdx->AddRef();
Stack[++StackPtr] = 0;
while (1)
{
dwRes = pIdx->FindKey(pszKey, pwID);
if (dwRes == 0)
{
// Found it
*pRetIdx = pIdx;
return NO_ERROR;
}
// Otherwise, we have to try to descend to a child page.
// =====================================================
dwPage = pIdx->GetPageId();
dwChildPage = pIdx->GetChildPage(*pwID);
if (dwChildPage == 0)
break;
pIdx->Release();
pIdx = 0;
Stack[++StackPtr] = dwPage;
dwRes = ReadIdxPage(dwChildPage, &pIdx);
if (dwRes)
return dwRes;
}
*pRetIdx = pIdx;
return ERROR_NOT_FOUND;
}
//***************************************************************************
//
// CBTree::InsertPhase2
//
// On entry, assumes that we have identified the page into which
// the insert must physically occur. This does the split + migrate
// logical to keep the tree in balance.
//
// Algorithm: Add key to page. If it does not overflow, we are done.
// If overflow occurs, allocate a new sibling page which will acquire
// half the keys from the current page. This sibling will be treated
// as lexically smaller in all cases. The median key is migrated
// up to the parent with pointers to both the new sibing page and
// the current page.
// The parent may also overflow. If so, the algorithm repeats.
// If an overflow occurs and there is no parent node (we are at the root)
// a new root node is allocated and the median key migrated into it.
//
//***************************************************************************
// ok
DWORD CBTree::InsertPhase2(
SIdxKeyTable *pCurrent,
WORD wID,
LPSTR pszKey,
DWORD dwValue,
DWORD Stack[],
LONG &StackPtr
)
{
DWORD dwRes;
// If non-NULL, used for a primary insert.
// If NULL, skip this, under the assumption the
// node is already up-to-date and merely requires
// the up-recursive split & migrate treatment.
// ==============================================
if (pszKey)
{
dwRes = pCurrent->AddKey(pszKey, wID, dwValue);
if (dwRes)
return dwRes; // Failed
}
pCurrent->AddRef(); // Makes following loop consistent
SIdxKeyTable *pSibling = 0;
SIdxKeyTable *pParent = 0;
// The class B-tree split+migration loop.
// ======================================
for (;;)
{
// Check the current node where we added the key.
// If it isn't too big, we're done.
// ==============================================
dwRes = pCurrent->GetRequiredPageMemory();
if (dwRes <= m_pSrc->GetPageSize())
{
dwRes = WriteIdxPage(pCurrent);
break;
}
// If here, it ain't gonna fit. We have to split the page.
// Allocate a new page (Sibling) and get the parent page, which
// will receive the median key.
// ============================================================
DWORD dwParent = Stack[StackPtr--];
if (dwParent == 0)
{
// Allocate a new page to become the parent.
LPDWORD pParentPg = 0;
dwRes = m_pSrc->NewPage((LPVOID *) &pParentPg);
if (dwRes)
break;
DWORD dwNewParent = pParentPg[CPageSource::OFFSET_PAGE_ID];
_BtrMemFree(pParentPg);
dwRes = SIdxKeyTable::Create(dwNewParent, &pParent);
if (dwRes)
break;
dwRes = m_pSrc->SetRootPage(dwNewParent);
if (dwRes)
break;
m_pRoot->Release(); // Replace old root
m_pRoot = pParent;
m_pRoot->AddRef();
}
else
{
if (dwParent == m_pRoot->GetPageId())
{
pParent = m_pRoot;
pParent->AddRef();
}
else
{
dwRes = ReadIdxPage(dwParent, &pParent);
if (dwRes)
break;
}
}
// Allocate a new sibling in any case to hold half the keys
// ========================================================
LPDWORD pSibPg = 0;
dwRes = m_pSrc->NewPage((LPVOID *) &pSibPg);
if (dwRes)
break;
DWORD dwNewSib = pSibPg[CPageSource::OFFSET_PAGE_ID];
_BtrMemFree(pSibPg);
dwRes = SIdxKeyTable::Create(dwNewSib, &pSibling);
if (dwRes)
break;
dwRes = pCurrent->Redist(pParent, pSibling);
if (dwRes)
break;
dwRes = WriteIdxPage(pCurrent);
dwRes |= WriteIdxPage(pSibling);
pCurrent->Release();
pCurrent = 0;
pSibling->Release();
pSibling = 0;
if (dwRes)
break;
pCurrent = pParent;
pParent = 0;
}
ReleaseIfNotNULL(pParent);
ReleaseIfNotNULL(pCurrent);
ReleaseIfNotNULL(pSibling);
return dwRes;
}
//***************************************************************************
//
// CBTree::WriteIdxPage
//
// Writes the object to the physical page it is assigned to.
// If the page ID is zero, then it is considered invalid. Further,
// while is it correct to precheck the page size, this function does
// validate with regard to sizes, etc.
//
//***************************************************************************
//
DWORD CBTree::WriteIdxPage(
SIdxKeyTable *pIdx
)
{
DWORD dwRes;
DWORD dwPageSize = m_pSrc->GetPageSize();
DWORD dwMem = pIdx->GetRequiredPageMemory();
if (dwMem > dwPageSize)
return ERROR_INVALID_PARAMETER;
LPVOID pMem = _BtrMemAlloc(dwPageSize);
if (pMem == 0)
return ERROR_NOT_ENOUGH_MEMORY;
dwRes = pIdx->MapToPage(pMem);
if (dwRes)
{
_BtrMemFree(pMem);
return dwRes;
}
dwRes = m_pSrc->PutPage(pMem, CPageSource::PAGE_TYPE_ACTIVE);
_BtrMemFree(pMem);
InterlockedIncrement(&m_lGeneration);
// Check for a root update.
// ========================
if (m_pRoot != pIdx && m_pRoot->GetPageId() == pIdx->GetPageId())
{
m_pRoot->Release();
m_pRoot = pIdx;
m_pRoot->AddRef();
if (m_pSrc->GetRootPage() != m_pRoot->GetPageId())
dwRes = m_pSrc->SetRootPage(m_pRoot->GetPageId());
}
return dwRes;
}
//***************************************************************************
//
// CBTree::ReadIdxPage
//
//***************************************************************************
//
DWORD CBTree::ReadIdxPage(
DWORD dwPage,
SIdxKeyTable **pIdx
)
{
DWORD dwRes;
LPVOID pPage = 0;
SIdxKeyTable *p = 0;
if (pIdx == 0)
return ERROR_INVALID_PARAMETER;
*pIdx = 0;
// if (dwPage < MAX_PAGE_HISTORY) // May remove if studies show no caching possible
// ++History[dwPage];
dwRes = m_pSrc->GetPage(dwPage, &pPage);
if (dwRes)
return dwRes;
dwRes = SIdxKeyTable::Create(pPage, &p);
if (dwRes)
{
_BtrMemFree(pPage);
return dwRes;
}
_BtrMemFree(pPage);
if (dwRes)
return dwRes;
*pIdx = p;
return dwRes;
}
//***************************************************************************
//
// CBTree::FindKey
//
// Does a simple search of a key, returning the user data, if requested.
//
// Typical Return values
// NO_ERROR
// ERROR_NOT_FOUND
//
//***************************************************************************
// ok
DWORD CBTree::FindKey(
IN LPSTR pszKey,
DWORD *pdwData
)
{
WORD wID;
DWORD dwRes;
SIdxKeyTable *pIdx = 0;
LONG StackPtr = -1;
DWORD Stack[CBTreeIterator::const_MaxStack];
if (pszKey == 0 || *pszKey == 0)
return ERROR_INVALID_PARAMETER;
// Search high and low, hoping against hope...
// ===========================================
dwRes = Search(pszKey, &pIdx, &wID, Stack, StackPtr);
if (dwRes == 0 && pdwData)
{
*pdwData = pIdx->GetUserData(wID);
}
// If here, we can indeed add it.
// ==============================
ReleaseIfNotNULL(pIdx);
return dwRes;
}
//***************************************************************************
//
// CBTree::DeleteKey
//
//***************************************************************************
//
DWORD CBTree::DeleteKey(
IN LPSTR pszKey
)
{
DWORD dwRes;
LONG StackPtr = -1;
DWORD Stack[CBTreeIterator::const_MaxStack];
SIdxKeyTable *pIdx = 0;
WORD wId;
DWORD dwLoad;
// Find it
// =======
dwRes = Search(pszKey, &pIdx, &wId, Stack, StackPtr);
if (dwRes)
return dwRes;
// Delete key from from page
// ==========================
if (pIdx->IsLeaf())
{
// A leaf node. Remove the key.
// =============================
dwRes = pIdx->RemoveKey(wId);
// Now, check the load and see if it has dropped below 30%.
// Of course, if we are at the root node and it is a leaf,
// we have to pretty much let it go as is...
// ========================================================
dwLoad = ComputeLoad(pIdx);
if (dwLoad > const_MinimumLoad ||
pIdx->GetPageId() == m_pRoot->GetPageId())
{
dwRes = WriteIdxPage(pIdx);
pIdx->Release();
return dwRes;
}
}
else
{
// An internal node, so we have to find the successor.
// Since this call may alter the shape of the tree quite
// a bit (the successor may overflow the affected node),
// we have to relocate the successor.
// ====================================================
LPSTR pszSuccessor = 0;
BOOL bUnderflow = FALSE;
dwRes = ReplaceBySuccessor(pIdx, wId, &pszSuccessor, &bUnderflow, Stack, StackPtr);
if (dwRes)
return dwRes;
dwRes = InsertPhase2(pIdx, 0, 0, 0, Stack, StackPtr);
if (dwRes)
return dwRes;
pIdx->Release();
pIdx = 0;
StackPtr = -1;
if (bUnderflow == FALSE)
{
_BtrMemFree(pszSuccessor);
return NO_ERROR;
}
// If here, the node we extracted the successor from was reduced
// to poverty and underflowed. We have to find it again and
// execute the underflow repair loop.
// =============================================================
dwRes = Search(pszSuccessor, &pIdx, &wId, Stack, StackPtr);
_BtrMemFree(pszSuccessor);
if (dwRes)
return dwRes;
SIdxKeyTable *pSuccessor = 0;
dwRes = FindSuccessorNode(pIdx, wId, &pSuccessor, 0, Stack, StackPtr);
if (dwRes)
return dwRes;
pIdx->Release();
pIdx = pSuccessor;
}
// UNDERFLOW REPAIR Loop.
// At this point <pIdx> points to the deepest affected node.
// We need to start working back up the tree and repairing
// the damage. Nodes which have reached zero in size are
// quite a pain. But they aren't half as bad as nodes which claim
// they can recombine with a sibling but really can't. So,
// we either do nothing (the node has enough stuff to be useful),
// collapse with a sibling node or borrow some keys from a sibling
// to ensure all nodes meet the minimum load requirement.
// ===============================================================
SIdxKeyTable *pSibling = 0;
SIdxKeyTable *pParent = 0;
for (;;)
{
DWORD dwParentId = Stack[StackPtr--];
DWORD dwThisId = pIdx->GetPageId();
dwLoad = ComputeLoad(pIdx);
if (dwLoad > const_MinimumLoad || dwParentId == 0)
{
dwRes = WriteIdxPage(pIdx);
pIdx->Release();
break;
}
// If here the node is getting small. We must collapsed this
// node with a sibling.
// collapse this node and a sibling
dwRes = ReadIdxPage(dwParentId, &pParent);
// Locate a sibling and see if the sibling and the current node
// can be collapsed with leftover space.
// =============================================================
DWORD dwLeftSibling = pParent->GetLeftSiblingOf(pIdx->GetPageId());
DWORD dwRightSibling = pParent->GetRightSiblingOf(pIdx->GetPageId());
DWORD dwSiblingId = 0;
if (dwLeftSibling)
{
dwRes = ReadIdxPage(dwLeftSibling, &pSibling);
dwSiblingId = pSibling->GetPageId();
}
else
{
dwRes = ReadIdxPage(dwRightSibling, &pSibling);
dwSiblingId = pSibling->GetPageId();
}
// If here, the node is 'underloaded'. Now we have to
// get the parent and the sibling and collapsed them.
// ===================================================
SIdxKeyTable *pCopy = 0;
pIdx->Clone(&pCopy);
dwRes = pIdx->Collapse(pParent, pSibling);
// Now we have a different sort of problem, possibly.
// If the collapsed node is too big, we have to try
// a different strategy.
// ===================================================
if (pIdx->GetRequiredPageMemory() > m_pSrc->GetPageSize())
{
pIdx->Release();
pParent->Release();
pSibling->Release();
pIdx = pParent = pSibling = 0;
// Reread the pages.
// =================
pIdx = pCopy;
dwRes = ReadIdxPage(dwParentId, &pParent);
dwRes = ReadIdxPage(dwSiblingId, &pSibling);
// Transfer a key or two from sibling via parent.
// This doesn't change the tree shape, but the
// parent may overflow.
// ==============================================
do
{
dwRes = pIdx->StealKeyFromSibling(pParent, pSibling);
dwLoad = ComputeLoad(pIdx);
} while (dwLoad < const_MinimumLoad);
dwRes = WriteIdxPage(pIdx);
pIdx->Release();
dwRes = WriteIdxPage(pSibling);
pSibling->Release();
dwRes = InsertPhase2(pParent, 0, 0, 0, Stack, StackPtr);
pParent->Release();
break;
}
else // The collapse worked; we can free the sibling page
{
pCopy->Release();
dwRes = m_pSrc->FreePage(pSibling->GetPageId());
pSibling->Release();
}
// If here, the collapse worked.
// =============================
dwRes = WriteIdxPage(pIdx);
if (dwRes)
{
pIdx->Release();
break;
}
if (pParent->GetNumKeys() == 0)
{
// We have replaced the root. Note
// that we transfer the ref count of pIdx to m_pRoot.
DWORD dwOldRootId = m_pRoot->GetPageId();
m_pRoot->Release();
m_pRoot = pIdx;
// Even though we wrote <pIdx> a few lines back,
// a rewrite is required to update internal stuff
// because this has become the new root.
// ==============================================
m_pSrc->SetRootPage(m_pRoot->GetPageId());
dwRes = WriteIdxPage(m_pRoot);
m_pSrc->FreePage(dwOldRootId);
pParent->Release();
break;
}
pIdx->Release();
pIdx = pParent;
}
return dwRes;
}
//***************************************************************************
//
// CBTree::ReplaceBySuccessor
//
// Removes the wId key in the <pIdx> node, and replaces it with the
// successor.
//
// Precondition: <pIdx> is an internal (non-leaf) node.
//
// Side-effects: <pIdx> may be overflowed and require the InsertPhase2
// treatment. The node from which the successor is extracted is
// written, but may have been reduced to zero keys.
//
//***************************************************************************
//
DWORD CBTree::ReplaceBySuccessor(
IN SIdxKeyTable *pIdx,
IN WORD wId,
OUT LPSTR *pszSuccessorKey,
OUT BOOL *pbUnderflowDetected,
DWORD Stack[],
LONG &StackPtr
)
{
SIdxKeyTable *pTemp = 0;
DWORD dwRes;
DWORD dwPredecessorChild;
dwRes = FindSuccessorNode(pIdx, wId, &pTemp, &dwPredecessorChild, Stack, StackPtr);
if (dwRes || pTemp == 0)
return dwRes;
LPSTR pszKey = 0;
pTemp->GetKeyAt(0, &pszKey);
DWORD dwUserData = pTemp->GetUserData(0);
pTemp->RemoveKey(0);
if (ComputeLoad(pTemp) < const_MinimumLoad)
*pbUnderflowDetected = TRUE;
dwRes = WriteIdxPage(pTemp);
pTemp->Release();
pIdx->RemoveKey(wId);
pIdx->AddKey(pszKey, wId, dwUserData);
pIdx->SetChildPage(wId, dwPredecessorChild);
*pszSuccessorKey = pszKey;
StackPtr--;
return dwRes;
}
//***************************************************************************
//
// CBTree::FindSuccessorNode
//
// Read-only. Finds the node containing the successor to the specified key.
//
//***************************************************************************
//
DWORD CBTree::FindSuccessorNode(
IN SIdxKeyTable *pIdx,
IN WORD wId,
OUT SIdxKeyTable **pSuccessor,
OUT DWORD *pdwPredecessorChild,
DWORD Stack[],
LONG &StackPtr
)
{
SIdxKeyTable *pTemp = 0;
DWORD dwRes;
DWORD dwSuccessorChild, dwPredecessorChild;
dwPredecessorChild = pIdx->GetChildPage(wId);
dwSuccessorChild = pIdx->GetChildPage(wId+1);
Stack[++StackPtr] = pIdx->GetPageId();
// From this point on, take leftmost children until
// we reach a leaf node. The leftmost key in the
// leftmost node is always the successor, thanks to the
// astonishing properties of the BTree. Nice and easy, huh?
// =========================================================
while (dwSuccessorChild)
{
Stack[++StackPtr] = dwSuccessorChild;
if (pTemp)
pTemp->Release();
dwRes = ReadIdxPage(dwSuccessorChild, &pTemp);
dwSuccessorChild = pTemp->GetChildPage(0);
}
StackPtr--; // Pop the element we are returning in <*pSuccessor>
*pSuccessor = pTemp;
if (pdwPredecessorChild)
*pdwPredecessorChild = dwPredecessorChild;
return dwRes;
}
//***************************************************************************
//
// CBTree::BeginEnum
//
//***************************************************************************
//
DWORD CBTree::BeginEnum(
LPSTR pszStartKey,
OUT CBTreeIterator **pIterator
)
{
CBTreeIterator *pIt = new CBTreeIterator;
if (pIt == 0)
return ERROR_NOT_ENOUGH_MEMORY;
DWORD dwRes = pIt->Init(this, pszStartKey);
if (dwRes)
{
pIt->Release();
return dwRes;
}
*pIterator = pIt;
return NO_ERROR;
}
//***************************************************************************
//
// CBTree::Dump
//
//***************************************************************************
//
void CBTree::Dump(FILE *f)
{
m_pSrc->Dump(f);
}
//***************************************************************************
//
//***************************************************************************
//
DWORD CBTree::InvalidateCache()
{
m_pRoot->Release();
DWORD dwRootPage = m_pSrc->GetRootPage();
DWORD dwRes = ReadIdxPage(dwRootPage, &m_pRoot);
return dwRes;
}
//***************************************************************************
//
// CBTreeIterator::Init
//
//***************************************************************************
//
DWORD CBTreeIterator::Init(
IN CBTree *pTree,
IN LPSTR pszStartKey
)
{
if (pTree == 0)
return ERROR_INVALID_PARAMETER;
m_pTree = pTree;
// Special case of enumerating everything. Probably not useful
// for WMI, but great for testing & debugging (I think).
// ============================================================
if (pszStartKey == 0)
{
Push(0, 0); // Sentinel value in stack
SIdxKeyTable *pRoot = pTree->m_pRoot;
pRoot->AddRef();
Push(pRoot, 0);
DWORD dwChildPage = Peek()->GetChildPage(0);
while (dwChildPage)
{
SIdxKeyTable *pIdx = 0;
DWORD dwRes = m_pTree->ReadIdxPage(dwChildPage, &pIdx);
if (dwRes)
return dwRes;
if (StackFull())
{
pIdx->Release();
return ERROR_INSUFFICIENT_BUFFER;
}
Push(pIdx, 0);
dwChildPage = pIdx->GetChildPage(0);
}
return NO_ERROR;
}
// If here, a matching string was specified.
// This is the typical case.
// =========================================
Push(0, 0); // Sentinel value in stack
WORD wId;
DWORD dwRes, dwChildPage;
SIdxKeyTable *pIdx = pTree->m_pRoot;
pIdx->AddRef();
while (1)
{
dwRes = pIdx->FindKey(pszStartKey, &wId);
if (dwRes == 0)
{
// Found it
Push(pIdx, wId);
return NO_ERROR;
}
// Otherwise, we have to try to descend to a child page.
// =====================================================
dwChildPage = pIdx->GetChildPage(wId);
if (dwChildPage == 0)
break;
Push(pIdx, wId);
pIdx = 0;
dwRes = pTree->ReadIdxPage(dwChildPage, &pIdx);
if (dwRes)
return dwRes;
}
Push(pIdx, wId);
while (Peek() && PeekId() == WORD(Peek()->GetNumKeys()))
Pop();
return NO_ERROR;
}
//***************************************************************************
//
// CBTreeIterator::Next
//
// On entry:
// <wID> is the key to visit in the current node (top-of-stack).
// The call sets up the successor before leaving. If there is no successor,
// the top of stack is left at NULL and ERROR_NO_MORE_ITEMS is returned.
//
// Returns ERROR_NO_MORE_ITEMS when the iteration is complete.
//
//***************************************************************************
//
DWORD CBTreeIterator::Next(
LPSTR *ppszStr,
DWORD *pdwData
)
{
DWORD dwRes;
if (ppszStr == 0)
return ERROR_INVALID_PARAMETER;
*ppszStr = 0;
if (Peek() == 0)
return ERROR_NO_MORE_ITEMS;
// Get the item for the caller.
// ============================
dwRes = Peek()->GetKeyAt(PeekId(), ppszStr);
if (dwRes)
return dwRes;
if (pdwData)
*pdwData = Peek()->GetUserData(PeekId());
IncStackId();
// Now find the successor.
// =======================
DWORD dwChildPage = Peek()->GetChildPage(PeekId());
while (dwChildPage)
{
SIdxKeyTable *pIdx = 0;
dwRes = m_pTree->ReadIdxPage(dwChildPage, &pIdx);
if (dwRes)
return dwRes;
if (StackFull())
{
pIdx->Release();
return ERROR_INSUFFICIENT_BUFFER;
}
Push(pIdx, 0);
dwChildPage = pIdx->GetChildPage(0);
}
// If here, we are at a leaf node.
// ===============================
while (Peek() && PeekId() == WORD(Peek()->GetNumKeys()))
Pop();
return NO_ERROR;
}
//***************************************************************************
//
// CBTreeIterator::Release
//
//***************************************************************************
//
DWORD CBTreeIterator::Release()
{
delete this;
return 0;
}
//***************************************************************************
//
// CBTreeIterator::~CBTreeIterator
//
//***************************************************************************
//
CBTreeIterator::~CBTreeIterator()
{
// Cleanup any leftover stack
while (m_lStackPointer > -1)
Pop();
}