742 lines
16 KiB
C
742 lines
16 KiB
C
/*++
|
|
|
|
Copyright (c) 1996 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
hash.c
|
|
|
|
Abstract:
|
|
|
|
Hashing routines used to speed lookup of memdb keys.
|
|
|
|
Author:
|
|
|
|
Jim Schmidt (jimschm) 8-Aug-1996
|
|
|
|
Revision History:
|
|
|
|
Jim Schmidt (jimschm) 21-Oct-1997 Split from memdb.c
|
|
|
|
--*/
|
|
|
|
#include "pch.h"
|
|
#include "memdbp.h"
|
|
|
|
#ifndef UNICODE
|
|
#error UNICODE required
|
|
#endif
|
|
|
|
#define DBG_MEMDB "MemDb"
|
|
|
|
//
|
|
// Globals
|
|
//
|
|
|
|
DWORD g_HashSize;
|
|
DWORD g_HashEnd;
|
|
DWORD g_HashFreeHead;
|
|
PBYTE g_HashBuf;
|
|
|
|
|
|
//
|
|
// #defines
|
|
//
|
|
|
|
// see memdbp.h for bit restrictions
|
|
#define INVALID_OFFSET_MASKED (INVALID_OFFSET & OFFSET_MASK)
|
|
#define ASSERT_OFFSET_ONLY(x) MYASSERT(((x) & RESERVED_MASK) == 0 || (x) == INVALID_OFFSET)
|
|
#define UNMASK_OFFSET(x) ((x)==INVALID_OFFSET_MASKED ? INVALID_OFFSET : (x))
|
|
#define MASK_OFFSET(x) ((x) & OFFSET_MASK)
|
|
|
|
#define MASK_4BIT 0x0000000f
|
|
#define INVALID_OFFSET_4BIT (INVALID_OFFSET & MASK_4BIT)
|
|
#define ASSERT_4BIT(x) MYASSERT(((x) & (~MASK_4BIT)) == 0 || (x) == INVALID_OFFSET)
|
|
#define CONVERT_4TO8(x) ((BYTE) ((x)==INVALID_OFFSET_4BIT ? INVALID_OFFSET : (x)))
|
|
#define CONVERT_8TO4(x) ((x) & MASK_4BIT)
|
|
|
|
#define HASH_BUCKETS 39989
|
|
#define HASH_BLOCK_SIZE (HASH_BUCKETS * sizeof (BUCKETSTRUCT))
|
|
#define HASHBUFPTR(offset) ((PBUCKETSTRUCT) (g_HashBuf + offset))
|
|
|
|
//
|
|
// Local privates
|
|
//
|
|
|
|
VOID
|
|
pResetHashBlock (
|
|
VOID
|
|
);
|
|
|
|
|
|
//
|
|
// Implementation
|
|
//
|
|
|
|
BOOL
|
|
InitializeHashBlock (
|
|
VOID
|
|
)
|
|
{
|
|
g_HashSize = HASH_BLOCK_SIZE * 2;
|
|
|
|
g_HashBuf = (PBYTE) MemAlloc (g_hHeap, 0, g_HashSize);
|
|
pResetHashBlock();
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
VOID
|
|
pResetHashBlock (
|
|
VOID
|
|
)
|
|
{
|
|
PBUCKETSTRUCT BucketPtr;
|
|
INT i;
|
|
|
|
g_HashEnd = HASH_BLOCK_SIZE;
|
|
g_HashFreeHead = INVALID_OFFSET;
|
|
|
|
BucketPtr = (PBUCKETSTRUCT) g_HashBuf;
|
|
for (i = 0 ; i < HASH_BUCKETS ; i++) {
|
|
BucketPtr->Offset = INVALID_OFFSET;
|
|
BucketPtr->Info.NextItem = INVALID_OFFSET_MASKED;
|
|
BucketPtr->Info.Hive = 0;
|
|
BucketPtr++;
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
FreeHashBlock (
|
|
VOID
|
|
)
|
|
{
|
|
if (g_HashBuf) {
|
|
MemFree (g_hHeap, 0, g_HashBuf);
|
|
g_HashBuf = NULL;
|
|
}
|
|
|
|
g_HashSize = 0;
|
|
g_HashEnd = 0;
|
|
g_HashFreeHead = INVALID_OFFSET;
|
|
}
|
|
|
|
|
|
BOOL
|
|
EnumFirstHashEntry (
|
|
OUT PHASHENUM EnumPtr
|
|
)
|
|
{
|
|
ZeroMemory (EnumPtr, sizeof (HASHENUM));
|
|
|
|
return EnumNextHashEntry (EnumPtr);
|
|
}
|
|
|
|
|
|
BOOL
|
|
EnumNextHashEntry (
|
|
IN OUT PHASHENUM EnumPtr
|
|
)
|
|
{
|
|
for (;;) {
|
|
if (EnumPtr->Bucket == HASH_BUCKETS) {
|
|
//
|
|
// The completion case
|
|
//
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
if (!EnumPtr->BucketPtr) {
|
|
//
|
|
// This case occurs when we are begining to enumerate a bucket
|
|
//
|
|
|
|
EnumPtr->BucketPtr = (PBUCKETSTRUCT) g_HashBuf + EnumPtr->Bucket;
|
|
if (EnumPtr->BucketPtr->Offset == INVALID_OFFSET) {
|
|
EnumPtr->BucketPtr = NULL;
|
|
EnumPtr->Bucket += 1;
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Return this first item in the bucket
|
|
//
|
|
|
|
EnumPtr->LastOffset = EnumPtr->BucketPtr->Offset;
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// This case occurs when we are continuing enumeration of a bucket
|
|
//
|
|
|
|
if (EnumPtr->BucketPtr->Offset == INVALID_OFFSET) {
|
|
//
|
|
// Current bucket item (and also the last bucket item) may have
|
|
// been deleted -- check that now
|
|
//
|
|
|
|
if (!EnumPtr->PrevBucketPtr) {
|
|
//
|
|
// Last item has been deleted; continue to next bucket
|
|
//
|
|
|
|
EnumPtr->BucketPtr = NULL;
|
|
EnumPtr->Bucket += 1;
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Previous bucket item is valid; use it.
|
|
//
|
|
|
|
EnumPtr->BucketPtr = EnumPtr->PrevBucketPtr;
|
|
|
|
} else {
|
|
//
|
|
// Current bucket item may have been deleted, but another item was
|
|
// moved to its place -- check that now
|
|
//
|
|
|
|
if (EnumPtr->BucketPtr->Offset != EnumPtr->LastOffset) {
|
|
EnumPtr->LastOffset = EnumPtr->BucketPtr->Offset;
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// We now know that the current bucket item was not changed, so it
|
|
// becomes our previous item and we move on to the next item (if
|
|
// one exists)
|
|
//
|
|
|
|
if (UNMASK_OFFSET (EnumPtr->BucketPtr->Info.NextItem) == INVALID_OFFSET) {
|
|
//
|
|
// End of bucket reached
|
|
//
|
|
|
|
EnumPtr->BucketPtr = NULL;
|
|
EnumPtr->Bucket += 1;
|
|
continue;
|
|
}
|
|
|
|
EnumPtr->PrevBucketPtr = EnumPtr->BucketPtr;
|
|
EnumPtr->BucketPtr = HASHBUFPTR (UNMASK_OFFSET (EnumPtr->BucketPtr->Info.NextItem));
|
|
|
|
|
|
EnumPtr->LastOffset = EnumPtr->BucketPtr->Offset;
|
|
MYASSERT(EnumPtr->LastOffset != INVALID_OFFSET);
|
|
break;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
typedef struct {
|
|
BYTE Hive;
|
|
DWORD Offset;
|
|
} HASH_ITEM, *PHASH_ITEM;
|
|
|
|
BOOL
|
|
SaveHashBlock (
|
|
HANDLE File
|
|
)
|
|
{
|
|
BOOL b;
|
|
DWORD Written;
|
|
PBYTE BackupBlock;
|
|
UINT OrgEnd, OrgSize, OrgFreeHead;
|
|
PBYTE OrgBlock;
|
|
WCHAR TempStr[MEMDB_MAX];
|
|
GROWBUFFER GrowBuf = GROWBUF_INIT;
|
|
HASHENUM e;
|
|
PHASH_ITEM ItemPtr;
|
|
UINT u;
|
|
|
|
//
|
|
// Back up the hash block
|
|
//
|
|
|
|
BackupBlock = MemAlloc (g_hHeap, 0, g_HashEnd);
|
|
CopyMemory (BackupBlock, g_HashBuf, g_HashEnd);
|
|
|
|
OrgEnd = g_HashEnd;
|
|
OrgSize = g_HashSize;
|
|
OrgFreeHead = g_HashFreeHead;
|
|
OrgBlock = g_HashBuf;
|
|
|
|
g_HashBuf = BackupBlock;
|
|
|
|
//
|
|
// Delete all hash entries that do not belong to the root database.
|
|
// Do this by queueing the hash entry removal, so the EnumNextHashEntry
|
|
// function will continue to work.
|
|
//
|
|
|
|
if (EnumFirstHashEntry (&e)) {
|
|
do {
|
|
|
|
if (e.BucketPtr->Info.Hive) {
|
|
ItemPtr = (PHASH_ITEM) GrowBuffer (&GrowBuf, sizeof (HASH_ITEM));
|
|
ItemPtr->Hive = (BYTE) (e.BucketPtr->Info.Hive);
|
|
ItemPtr->Offset = e.BucketPtr->Offset;
|
|
}
|
|
|
|
} while (EnumNextHashEntry (&e));
|
|
}
|
|
|
|
ItemPtr = (PHASH_ITEM) GrowBuf.Buf;
|
|
|
|
for (u = 0 ; u < GrowBuf.End ; u += sizeof (HASH_ITEM), ItemPtr++) {
|
|
|
|
SelectDatabase (ItemPtr->Hive);
|
|
|
|
if (PrivateBuildKeyFromOffset (
|
|
0,
|
|
ItemPtr->Offset,
|
|
TempStr,
|
|
NULL,
|
|
NULL,
|
|
NULL
|
|
)) {
|
|
|
|
RemoveHashTableEntry (TempStr);
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Write the hash block end and deleted pointer
|
|
//
|
|
|
|
b = WriteFile (File, &g_HashEnd, sizeof (DWORD), &Written, NULL);
|
|
|
|
if (b) {
|
|
b = WriteFile (File, &g_HashFreeHead, sizeof (DWORD), &Written, NULL);
|
|
}
|
|
|
|
//
|
|
// Write the hash block
|
|
//
|
|
|
|
if (b) {
|
|
b = WriteFile (File, g_HashBuf, g_HashEnd, &Written, NULL);
|
|
if (Written != g_HashEnd) {
|
|
b = FALSE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Restore the hash block
|
|
//
|
|
|
|
PushError();
|
|
|
|
g_HashEnd = OrgEnd;
|
|
g_HashSize = OrgSize;
|
|
g_HashFreeHead = OrgFreeHead;
|
|
g_HashBuf = OrgBlock;
|
|
|
|
SelectDatabase (0);
|
|
|
|
MemFree (g_hHeap, 0, BackupBlock);
|
|
|
|
PopError();
|
|
|
|
return b;
|
|
}
|
|
|
|
|
|
BOOL
|
|
LoadHashBlock (
|
|
HANDLE File
|
|
)
|
|
{
|
|
BOOL b;
|
|
DWORD Read;
|
|
PBYTE TempBuf = NULL;
|
|
|
|
//
|
|
// Read the hash block end and deleted pointer; allocate memory for block.
|
|
//
|
|
|
|
b = ReadFile (File, &g_HashEnd, sizeof (DWORD), &Read, NULL);
|
|
|
|
if (b) {
|
|
b = ReadFile (File, &g_HashFreeHead, sizeof (DWORD), &Read, NULL);
|
|
}
|
|
|
|
if (b) {
|
|
g_HashSize = g_HashEnd;
|
|
|
|
TempBuf = (PBYTE) MemAlloc (g_hHeap, 0, g_HashSize);
|
|
if (TempBuf) {
|
|
if (g_HashBuf) {
|
|
MemFree (g_hHeap, 0, g_HashBuf);
|
|
}
|
|
|
|
g_HashBuf = TempBuf;
|
|
TempBuf = NULL;
|
|
} else {
|
|
b = FALSE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Read the hash block
|
|
//
|
|
|
|
if (b) {
|
|
b = ReadFile (File, g_HashBuf, g_HashSize, &Read, NULL);
|
|
if (Read != g_HashSize) {
|
|
b = FALSE;
|
|
SetLastError (ERROR_BAD_FORMAT);
|
|
}
|
|
}
|
|
|
|
return b;
|
|
}
|
|
|
|
|
|
DWORD
|
|
pCalculateHashVal (
|
|
IN PCWSTR String
|
|
)
|
|
{
|
|
DWORD Hash = 0;
|
|
|
|
while (*String) {
|
|
Hash = (Hash << 3) | (Hash >> 29);
|
|
Hash += towlower (*String);
|
|
String++;
|
|
}
|
|
|
|
Hash %= HASH_BUCKETS;
|
|
|
|
return Hash;
|
|
}
|
|
|
|
DWORD
|
|
pAllocBucket (
|
|
VOID
|
|
)
|
|
{
|
|
DWORD rBucketOffset;
|
|
PBYTE TempBuf;
|
|
PBUCKETSTRUCT BucketPtr;
|
|
|
|
if (g_HashFreeHead != INVALID_OFFSET) {
|
|
rBucketOffset = g_HashFreeHead;
|
|
BucketPtr = HASHBUFPTR (rBucketOffset);
|
|
g_HashFreeHead = UNMASK_OFFSET (BucketPtr->Info.NextItem);
|
|
|
|
MYASSERT (rBucketOffset < g_HashEnd);
|
|
} else {
|
|
|
|
if (g_HashEnd + sizeof (BUCKETSTRUCT) > g_HashSize) {
|
|
g_HashSize += HASH_BLOCK_SIZE;
|
|
TempBuf = MemReAlloc (g_hHeap, 0, g_HashBuf, g_HashSize);
|
|
DEBUGMSG ((DBG_NAUSEA, "Realloc'd memdb hash table"));
|
|
|
|
if (!TempBuf) {
|
|
DEBUGMSG ((DBG_ERROR, "Out of memory!"));
|
|
g_HashSize -= HASH_BLOCK_SIZE;
|
|
return INVALID_OFFSET;
|
|
}
|
|
|
|
g_HashBuf = TempBuf;
|
|
}
|
|
|
|
rBucketOffset = g_HashEnd;
|
|
g_HashEnd += sizeof (BUCKETSTRUCT);
|
|
|
|
BucketPtr = HASHBUFPTR (rBucketOffset);
|
|
}
|
|
|
|
BucketPtr->Offset = INVALID_OFFSET;
|
|
BucketPtr->Info.NextItem = INVALID_OFFSET_MASKED;
|
|
|
|
ASSERT_4BIT (g_SelectedDatabase);
|
|
BucketPtr->Info.Hive = CONVERT_8TO4 (g_SelectedDatabase);
|
|
|
|
return rBucketOffset;
|
|
}
|
|
|
|
|
|
BOOL
|
|
AddHashTableEntry (
|
|
IN PCWSTR FullString,
|
|
IN DWORD Offset
|
|
)
|
|
{
|
|
DWORD Bucket;
|
|
PBUCKETSTRUCT BucketPtr, PrevBucketPtr;
|
|
DWORD BucketOffset;
|
|
DWORD NewOffset;
|
|
DWORD PrevBucketOffset;
|
|
|
|
Bucket = pCalculateHashVal (FullString);
|
|
BucketPtr = (PBUCKETSTRUCT) g_HashBuf + Bucket;
|
|
|
|
//
|
|
// See if root bucket item has been used or not
|
|
//
|
|
|
|
if (BucketPtr->Offset != INVALID_OFFSET) {
|
|
//
|
|
// Yes - add to end of the chain
|
|
//
|
|
|
|
BucketOffset = Bucket * sizeof (BUCKETSTRUCT);
|
|
do {
|
|
BucketPtr = HASHBUFPTR (BucketOffset);
|
|
PrevBucketOffset = BucketOffset;
|
|
BucketOffset = UNMASK_OFFSET (BucketPtr->Info.NextItem);
|
|
} while (BucketOffset != INVALID_OFFSET);
|
|
|
|
|
|
//
|
|
// Add to the chain
|
|
//
|
|
|
|
NewOffset = pAllocBucket();
|
|
PrevBucketPtr = HASHBUFPTR (PrevBucketOffset);
|
|
ASSERT_OFFSET_ONLY (NewOffset);
|
|
PrevBucketPtr->Info.NextItem = MASK_OFFSET (NewOffset);
|
|
|
|
if (NewOffset == INVALID_OFFSET) {
|
|
return FALSE;
|
|
}
|
|
|
|
BucketPtr = HASHBUFPTR (NewOffset);
|
|
MYASSERT (BucketPtr->Info.NextItem == INVALID_OFFSET_MASKED);
|
|
}
|
|
|
|
BucketPtr->Offset = Offset;
|
|
ASSERT_4BIT (g_SelectedDatabase);
|
|
BucketPtr->Info.Hive = CONVERT_8TO4 (g_SelectedDatabase);
|
|
|
|
#ifdef DEBUG
|
|
{
|
|
DWORD HashOffset;
|
|
|
|
HashOffset = FindStringInHashTable (FullString, NULL);
|
|
MYASSERT (HashOffset != INVALID_OFFSET);
|
|
DEBUGMSG_IF ((HashOffset != Offset, DBG_MEMDB, "Duplicate in hash table: %s", FullString));
|
|
}
|
|
#endif
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
PBUCKETSTRUCT
|
|
pFindBucketItemInHashTable (
|
|
IN PCWSTR FullString,
|
|
OUT PBUCKETSTRUCT *PrevBucketPtr, OPTIONAL
|
|
OUT DWORD *HashOffsetPtr OPTIONAL
|
|
)
|
|
{
|
|
DWORD Bucket;
|
|
DWORD BucketOffset;
|
|
PBUCKETSTRUCT BucketPtr = NULL;
|
|
WCHAR TempStr[MEMDB_MAX];
|
|
|
|
Bucket = pCalculateHashVal (FullString);
|
|
BucketOffset = Bucket * sizeof (BUCKETSTRUCT);
|
|
|
|
#ifdef MEMORY_TRACKING
|
|
{
|
|
//
|
|
// Circular link check
|
|
//
|
|
|
|
DWORD Prev, Next;
|
|
DWORD Turtle, Rabbit;
|
|
BOOL Even = FALSE;
|
|
|
|
Rabbit = BucketOffset;
|
|
Turtle = Rabbit;
|
|
while (Rabbit != INVALID_OFFSET) {
|
|
// Make rabbit point to next item in chain
|
|
Prev = Rabbit;
|
|
BucketPtr = HASHBUFPTR (Rabbit);
|
|
Rabbit = UNMASK_OFFSET (BucketPtr->Info.NextItem);
|
|
|
|
// We should always be ahead of the turtle
|
|
if (Rabbit == Turtle) {
|
|
BucketPtr = HASHBUFPTR (Rabbit);
|
|
Next = UNMASK_OFFSET (BucketPtr->Info.NextItem);
|
|
DEBUGMSG ((
|
|
DBG_WHOOPS,
|
|
"Circular link detected in memdb hash table! Turtle=%u, Rabbit=%u, Next=%u, Prev=%u",
|
|
Turtle,
|
|
Rabbit,
|
|
Next,
|
|
Prev
|
|
));
|
|
|
|
return NULL;
|
|
}
|
|
|
|
// Make turtle point to next item in chain (1 of every 2 passes)
|
|
if (Even) {
|
|
BucketPtr = HASHBUFPTR (Turtle);
|
|
Turtle = UNMASK_OFFSET (BucketPtr->Info.NextItem);
|
|
}
|
|
|
|
Even = !Even;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
BucketPtr = HASHBUFPTR (BucketOffset);
|
|
|
|
if (PrevBucketPtr) {
|
|
*PrevBucketPtr = BucketPtr;
|
|
}
|
|
|
|
//
|
|
// If root bucket is not empty, scan bucket for FullString
|
|
//
|
|
|
|
if (BucketPtr->Offset != INVALID_OFFSET) {
|
|
do {
|
|
|
|
BucketPtr = HASHBUFPTR (BucketOffset);
|
|
ASSERT_4BIT (g_SelectedDatabase);
|
|
|
|
if (BucketPtr->Info.Hive == g_SelectedDatabase) {
|
|
//
|
|
// Build string using offset
|
|
//
|
|
|
|
PrivateBuildKeyFromOffset (
|
|
0,
|
|
BucketPtr->Offset,
|
|
TempStr,
|
|
NULL,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
//
|
|
// Do compare and return if match is found
|
|
//
|
|
|
|
if (StringIMatchW (FullString, TempStr)) {
|
|
if (HashOffsetPtr) {
|
|
*HashOffsetPtr = BucketOffset;
|
|
}
|
|
return BucketPtr;
|
|
}
|
|
|
|
}
|
|
|
|
if (PrevBucketPtr) {
|
|
*PrevBucketPtr = BucketPtr;
|
|
}
|
|
|
|
BucketOffset = UNMASK_OFFSET (BucketPtr->Info.NextItem);
|
|
|
|
} while (BucketOffset != INVALID_OFFSET);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
DWORD
|
|
FindStringInHashTable (
|
|
IN PCWSTR FullString,
|
|
OUT PBYTE DatabaseId OPTIONAL
|
|
)
|
|
{
|
|
PBUCKETSTRUCT BucketPtr;
|
|
|
|
BucketPtr = pFindBucketItemInHashTable (FullString, NULL, NULL);
|
|
if (BucketPtr) {
|
|
if (DatabaseId) {
|
|
*DatabaseId = (BYTE) (BucketPtr->Info.Hive);
|
|
}
|
|
|
|
return BucketPtr->Offset;
|
|
}
|
|
|
|
return INVALID_OFFSET;
|
|
}
|
|
|
|
|
|
BOOL
|
|
RemoveHashTableEntry (
|
|
IN PCWSTR FullString
|
|
)
|
|
{
|
|
PBUCKETSTRUCT BucketPtr;
|
|
PBUCKETSTRUCT PrevBucketPtr;
|
|
DWORD NextOffset;
|
|
PBUCKETSTRUCT NextBucketPtr;
|
|
DWORD BucketOffset;
|
|
|
|
BucketPtr = pFindBucketItemInHashTable (FullString, &PrevBucketPtr, &BucketOffset);
|
|
if (!BucketPtr) {
|
|
return FALSE;
|
|
}
|
|
|
|
if (PrevBucketPtr != BucketPtr) {
|
|
//
|
|
// If not at the first level (prev != current), give the block
|
|
// to free space.
|
|
//
|
|
|
|
PrevBucketPtr->Info.NextItem = BucketPtr->Info.NextItem;
|
|
ASSERT_OFFSET_ONLY (g_HashFreeHead);
|
|
BucketPtr->Info.NextItem = MASK_OFFSET (g_HashFreeHead);
|
|
BucketPtr->Offset = INVALID_OFFSET;
|
|
g_HashFreeHead = BucketOffset;
|
|
|
|
} else {
|
|
|
|
//
|
|
// Invalidate next item pointer if at the first level
|
|
//
|
|
|
|
if (UNMASK_OFFSET (BucketPtr->Info.NextItem) != INVALID_OFFSET) {
|
|
//
|
|
// Copy next item to root array
|
|
//
|
|
|
|
NextOffset = UNMASK_OFFSET (BucketPtr->Info.NextItem);
|
|
NextBucketPtr = HASHBUFPTR (NextOffset);
|
|
CopyMemory (BucketPtr, NextBucketPtr, sizeof (BUCKETSTRUCT));
|
|
|
|
//
|
|
// Donate next item to free space
|
|
//
|
|
|
|
ASSERT_OFFSET_ONLY (g_HashFreeHead);
|
|
NextBucketPtr->Info.NextItem = MASK_OFFSET (g_HashFreeHead);
|
|
NextBucketPtr->Offset = INVALID_OFFSET;
|
|
g_HashFreeHead = NextOffset;
|
|
|
|
|
|
} else {
|
|
//
|
|
// Delete of last item in bucket -- invalidate the root array item
|
|
//
|
|
|
|
BucketPtr->Info.NextItem = INVALID_OFFSET_MASKED;
|
|
BucketPtr->Offset = INVALID_OFFSET;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|