mirror of https://github.com/tongzx/nt5src
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.
1317 lines
31 KiB
1317 lines
31 KiB
/*++
|
|
|
|
Copyright (c) 1998 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
utilities.cpp
|
|
|
|
Abstract:
|
|
|
|
SIS Groveler utility functions
|
|
|
|
Authors:
|
|
|
|
Cedric Krumbein, 1998
|
|
|
|
Environment:
|
|
|
|
User Mode
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include "all.hxx"
|
|
|
|
/*****************************************************************************/
|
|
|
|
// GetPerformanceTime() converts the time interval
|
|
// measured using QueryPerformanceCounter() into milliseconds.
|
|
|
|
PerfTime GetPerformanceTime()
|
|
{
|
|
LARGE_INTEGER count;
|
|
QueryPerformanceCounter(&count);
|
|
return (PerfTime)count.QuadPart;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
// PerformanceTimeToMSec() converts the time interval measured
|
|
// using QueryPerformanceCounter() into milliseconds.
|
|
// PerformanceTimeToUSec() converts it into microseconds.
|
|
|
|
static DOUBLE frequency = 0.0;
|
|
|
|
DWORD PerformanceTimeToMSec(PerfTime timeInterval)
|
|
{
|
|
if (frequency == 0.0) {
|
|
LARGE_INTEGER intFreq;
|
|
QueryPerformanceFrequency(&intFreq);
|
|
frequency = (DOUBLE)intFreq.QuadPart;
|
|
}
|
|
|
|
return (DWORD)((DOUBLE)timeInterval * 1000.0 / frequency);
|
|
}
|
|
|
|
LONGLONG PerformanceTimeToUSec(PerfTime timeInterval)
|
|
{
|
|
if (frequency == 0.0) {
|
|
LARGE_INTEGER intFreq;
|
|
QueryPerformanceFrequency(&intFreq);
|
|
frequency = (DOUBLE)intFreq.QuadPart;
|
|
}
|
|
|
|
return (LONGLONG)((DOUBLE)timeInterval * 1000000.0 / frequency);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
// GetTime() returns the current file time.
|
|
|
|
DWORDLONG GetTime()
|
|
{
|
|
SYSTEMTIME systemTime;
|
|
|
|
FILETIME fileTime;
|
|
|
|
ULARGE_INTEGER time;
|
|
|
|
BOOL success;
|
|
|
|
GetSystemTime(&systemTime);
|
|
|
|
success = SystemTimeToFileTime(&systemTime, &fileTime);
|
|
ASSERT_ERROR(success);
|
|
|
|
time.HighPart = fileTime.dwHighDateTime;
|
|
time.LowPart = fileTime.dwLowDateTime;
|
|
|
|
return time.QuadPart;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
// PrintTime() converts the supplied file time into a printable string.
|
|
|
|
TCHAR *PrintTime(
|
|
TCHAR *string,
|
|
DWORDLONG time)
|
|
{
|
|
FILETIME fileTime;
|
|
|
|
SYSTEMTIME systemTime;
|
|
|
|
DWORD strLen;
|
|
|
|
BOOL success;
|
|
|
|
fileTime.dwHighDateTime = ((ULARGE_INTEGER *)&time)->HighPart;
|
|
fileTime.dwLowDateTime = ((ULARGE_INTEGER *)&time)->LowPart;
|
|
|
|
success = FileTimeToSystemTime(&fileTime, &systemTime);
|
|
ASSERT_ERROR(success);
|
|
|
|
strLen = _stprintf(string, _T("%02hu/%02hu/%02hu %02hu:%02hu:%02hu.%03hu"),
|
|
systemTime.wYear % 100,
|
|
systemTime.wMonth,
|
|
systemTime.wDay,
|
|
systemTime.wHour,
|
|
systemTime.wMinute,
|
|
systemTime.wSecond,
|
|
systemTime.wMilliseconds);
|
|
ASSERT(strLen == 21);
|
|
|
|
return string;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
// GetParentName() extracts the parent directory
|
|
// name out of a full-path file name.
|
|
|
|
BOOL GetParentName(
|
|
const TCHAR *fileName,
|
|
TFileName *parentName)
|
|
{
|
|
DWORD hi, lo;
|
|
|
|
ASSERT(fileName != NULL);
|
|
ASSERT(parentName != NULL);
|
|
|
|
if (fileName[0] == _T('\\'))
|
|
lo = 1;
|
|
else if (_istalpha(fileName[0])
|
|
&& fileName[1] == _T(':')
|
|
&& fileName[2] == _T('\\'))
|
|
lo = 3;
|
|
else
|
|
return FALSE;
|
|
|
|
hi = _tcslen(fileName) - 1;
|
|
if (hi < lo)
|
|
hi = lo;
|
|
else
|
|
for (; hi > lo; hi--)
|
|
if (fileName[hi] == _T('\\'))
|
|
break;
|
|
|
|
parentName->assign(fileName, hi);
|
|
return TRUE;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
// GetFileID gets the file's ID given its name.
|
|
|
|
DWORDLONG GetFileID(const TCHAR *fileName)
|
|
{
|
|
HANDLE fileHandle;
|
|
|
|
BY_HANDLE_FILE_INFORMATION fileInfo;
|
|
|
|
ULARGE_INTEGER fileID;
|
|
|
|
BOOL success;
|
|
|
|
ASSERT(fileName != NULL && fileName[0] != _T('\0'));
|
|
|
|
fileHandle = CreateFile(
|
|
fileName,
|
|
0,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_FLAG_BACKUP_SEMANTICS,
|
|
NULL);
|
|
if (fileHandle == INVALID_HANDLE_VALUE)
|
|
return 0;
|
|
|
|
if (GetFileInformationByHandle(fileHandle, &fileInfo)) {
|
|
fileID.HighPart = fileInfo.nFileIndexHigh;
|
|
fileID.LowPart = fileInfo.nFileIndexLow;
|
|
} else
|
|
fileID.QuadPart = 0;
|
|
|
|
success = CloseHandle(fileHandle);
|
|
ASSERT_ERROR(success);
|
|
|
|
return fileID.QuadPart;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
// GetFileName gets the file's name given either
|
|
// an open handle to the file or the file's ID.
|
|
|
|
BOOL GetFileName(
|
|
HANDLE fileHandle,
|
|
TFileName *tFileName)
|
|
#ifdef _UNICODE
|
|
{
|
|
IO_STATUS_BLOCK ioStatusBlock;
|
|
NTSTATUS ntStatus;
|
|
|
|
for (int i = 2; i > 0; --i) {
|
|
|
|
if (tFileName->nameLenMax < 8) // sanity check
|
|
tFileName->resize();
|
|
|
|
ntStatus = NtQueryInformationFile(
|
|
fileHandle,
|
|
&ioStatusBlock,
|
|
tFileName->nameInfo,
|
|
tFileName->nameInfoSize,
|
|
FileNameInformation);
|
|
|
|
if (ntStatus != STATUS_BUFFER_OVERFLOW)
|
|
break;
|
|
|
|
ASSERT(tFileName->nameInfo->FileNameLength > tFileName->nameInfoSize - sizeof(ULONG));
|
|
|
|
tFileName->resize(tFileName->nameInfo->FileNameLength / sizeof(WCHAR) + 1);
|
|
|
|
}
|
|
|
|
if (ntStatus != STATUS_SUCCESS)
|
|
return FALSE;
|
|
|
|
tFileName->nameLen = tFileName->nameInfo->FileNameLength / sizeof(WCHAR);
|
|
tFileName->name[tFileName->nameLen] = _T('\0');
|
|
|
|
return TRUE;
|
|
}
|
|
#else
|
|
{
|
|
IO_STATUS_BLOCK ioStatusBlock;
|
|
NTSTATUS ntStatus;
|
|
TFileName tempName;
|
|
ULONG nameLen;
|
|
|
|
for (int i = 2; i > 0; --i) {
|
|
|
|
ntStatus = NtQueryInformationFile(
|
|
fileHandle,
|
|
&ioStatusBlock,
|
|
tempName.nameInfo,
|
|
tempName.nameInfoSize - sizeof(WCHAR),
|
|
FileNameInformation);
|
|
|
|
if (ntStatus != STATUS_BUFFER_OVERFLOW)
|
|
break;
|
|
|
|
ASSERT(tempName.nameInfo->FileNameLength > tempName.nameInfoSize - sizeof(ULONG));
|
|
|
|
nameLen = tempName.nameInfo->FileNameLength / sizeof(WCHAR);
|
|
|
|
tempName.resize((tempName.nameInfo->FileNameLength + sizeof(WCHAR)) / sizeof(TCHAR));
|
|
}
|
|
|
|
if (ntStatus != STATUS_SUCCESS)
|
|
return FALSE;
|
|
|
|
tempName.nameInfo->FileName[nameLen] = UNICODE_NULL;
|
|
|
|
if (tFileName->nameLenMax < nameLen + 1)
|
|
tFileName->resize(nameLen + 1);
|
|
|
|
sprintf(tFileName->name, "%S", tempName.name);
|
|
tFileName->nameLen = nameLen;
|
|
|
|
return TRUE;
|
|
}
|
|
#endif
|
|
|
|
BOOL GetFileName(
|
|
HANDLE volumeHandle,
|
|
DWORDLONG fileID,
|
|
TFileName *tFileName)
|
|
{
|
|
UNICODE_STRING fileIDString;
|
|
|
|
OBJECT_ATTRIBUTES objectAttributes;
|
|
|
|
IO_STATUS_BLOCK ioStatusBlock;
|
|
|
|
HANDLE fileHandle;
|
|
|
|
NTSTATUS ntStatus;
|
|
|
|
BOOL success;
|
|
|
|
fileIDString.Length = sizeof(DWORDLONG);
|
|
fileIDString.MaximumLength = sizeof(DWORDLONG);
|
|
fileIDString.Buffer = (WCHAR *)&fileID;
|
|
|
|
objectAttributes.Length = sizeof(OBJECT_ATTRIBUTES);
|
|
objectAttributes.RootDirectory = volumeHandle;
|
|
objectAttributes.ObjectName = &fileIDString;
|
|
objectAttributes.Attributes = OBJ_CASE_INSENSITIVE;
|
|
objectAttributes.SecurityDescriptor = NULL;
|
|
objectAttributes.SecurityQualityOfService = NULL;
|
|
|
|
ntStatus = NtCreateFile(
|
|
&fileHandle,
|
|
GENERIC_READ,
|
|
&objectAttributes,
|
|
&ioStatusBlock,
|
|
NULL,
|
|
0,
|
|
FILE_SHARE_VALID_FLAGS,
|
|
FILE_OPEN,
|
|
FILE_OPEN_BY_FILE_ID |
|
|
FILE_OPEN_REPARSE_POINT |
|
|
FILE_NO_INTERMEDIATE_BUFFERING,
|
|
NULL,
|
|
0);
|
|
if (ntStatus != STATUS_SUCCESS)
|
|
return FALSE;
|
|
|
|
success = GetFileName(fileHandle, tFileName);
|
|
NtClose(fileHandle);
|
|
return success;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
// GetCSIndex() returns the SIS reparse point's common store
|
|
// index. The file handle must point to an open reparse point.
|
|
|
|
BOOL GetCSIndex(
|
|
HANDLE fileHandle,
|
|
CSID *csIndex)
|
|
{
|
|
IO_STATUS_BLOCK ioStatusBlock;
|
|
|
|
BYTE buffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
|
|
|
|
REPARSE_DATA_BUFFER *reparseBuffer;
|
|
|
|
SI_REPARSE_BUFFER *sisReparseBuffer;
|
|
|
|
ASSERT(fileHandle != NULL);
|
|
ASSERT(csIndex != NULL);
|
|
|
|
if (NtFsControlFile(
|
|
fileHandle,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&ioStatusBlock,
|
|
FSCTL_GET_REPARSE_POINT,
|
|
NULL,
|
|
0,
|
|
buffer,
|
|
MAXIMUM_REPARSE_DATA_BUFFER_SIZE) != STATUS_SUCCESS) {
|
|
memset(csIndex, 0, sizeof(CSID));
|
|
return FALSE;
|
|
}
|
|
|
|
reparseBuffer = (REPARSE_DATA_BUFFER *)buffer;
|
|
if (reparseBuffer->ReparseTag != IO_REPARSE_TAG_SIS) {
|
|
memset(csIndex, 0, sizeof(CSID));
|
|
return FALSE;
|
|
}
|
|
|
|
sisReparseBuffer = (SI_REPARSE_BUFFER *)
|
|
reparseBuffer->GenericReparseBuffer.DataBuffer;
|
|
|
|
if (sisReparseBuffer->ReparsePointFormatVersion != SIS_REPARSE_BUFFER_FORMAT_VERSION) {
|
|
memset(csIndex, 0, sizeof(CSID));
|
|
return FALSE;
|
|
}
|
|
|
|
*csIndex = sisReparseBuffer->CSid;
|
|
return TRUE;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
// GetCSName() converts the common store
|
|
// index into a dynamically allocated string.
|
|
|
|
TCHAR *GetCSName(CSID *csIndex)
|
|
{
|
|
TCHAR *rpcStr;
|
|
|
|
RPC_STATUS rpcStatus;
|
|
|
|
ASSERT(csIndex != NULL);
|
|
|
|
rpcStatus = UuidToString(csIndex, (unsigned short **)&rpcStr);
|
|
if (rpcStatus != RPC_S_OK) {
|
|
ASSERT(rpcStr == NULL);
|
|
return NULL;
|
|
}
|
|
|
|
ASSERT(rpcStr != NULL);
|
|
return rpcStr;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
// FreeCSName frees the string allocated by GetCSName().
|
|
|
|
VOID FreeCSName(TCHAR *rpcStr)
|
|
{
|
|
RPC_STATUS rpcStatus;
|
|
|
|
ASSERT(rpcStr != NULL);
|
|
|
|
rpcStatus = RpcStringFree((unsigned short **)&rpcStr);
|
|
ASSERT(rpcStatus == RPC_S_OK);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
// Checksum() generates a checksum on the data supplied in the buffer.
|
|
// The checksum function used is selected at compile-time; currently
|
|
// the 131-hash and the "Bill 32" hash functions are implemented.
|
|
|
|
#define HASH131
|
|
// #define BILL32HASH
|
|
|
|
Signature Checksum(
|
|
const VOID *buffer,
|
|
DWORD bufferLen,
|
|
DWORDLONG offset,
|
|
Signature firstWord)
|
|
{
|
|
Signature *bufferPtr,
|
|
word,
|
|
signature;
|
|
|
|
DWORD numWords,
|
|
numBytes,
|
|
rotate;
|
|
|
|
ASSERT(buffer != NULL);
|
|
|
|
bufferPtr = (Signature *)buffer;
|
|
numWords = bufferLen / sizeof(Signature);
|
|
numBytes = bufferLen % sizeof(Signature);
|
|
signature = firstWord;
|
|
|
|
#ifdef BILL32HASH
|
|
rotate = (DWORD)(offset / sizeof(Signature) % (sizeof(Signature)*8-1));
|
|
#endif
|
|
|
|
while (numWords-- > 0) {
|
|
word = *bufferPtr++;
|
|
#ifdef HASH131
|
|
signature = signature * 131 + word;
|
|
#endif
|
|
#ifdef BILL32HASH
|
|
signature ^= ROTATE_RIGHT(word, rotate);
|
|
rotate = (rotate+1) % (sizeof(Signature)*8-1);
|
|
#endif
|
|
}
|
|
|
|
if (numBytes > 0) {
|
|
word = 0;
|
|
memcpy(&word, bufferPtr, numBytes);
|
|
#ifdef HASH131
|
|
signature = signature * 131 + word;
|
|
#endif
|
|
#ifdef BILL32HASH
|
|
signature ^= ROTATE_RIGHT(word, rotate);
|
|
#endif
|
|
}
|
|
|
|
return signature;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/************************ Table class private methods ************************/
|
|
/*****************************************************************************/
|
|
|
|
DWORD Table::Hash(
|
|
const VOID *key,
|
|
DWORD keyLen) const
|
|
{
|
|
USHORT *keyPtr;
|
|
|
|
DWORD hashValue;
|
|
|
|
if (keyLen == 0)
|
|
return 0;
|
|
|
|
ASSERT(key != NULL);
|
|
|
|
if (keyLen <= sizeof(DWORD)) {
|
|
hashValue = 0;
|
|
memcpy(&hashValue, key, keyLen);
|
|
return hashValue;
|
|
}
|
|
|
|
keyPtr = (USHORT *)key;
|
|
hashValue = 0;
|
|
|
|
while (keyLen >= sizeof(USHORT)) {
|
|
hashValue = hashValue*37 + (DWORD)*keyPtr++;
|
|
keyLen -= sizeof(USHORT);
|
|
}
|
|
|
|
if (keyLen > 0)
|
|
hashValue = hashValue*37 + (DWORD)*(BYTE *)keyPtr;
|
|
|
|
hashValue *= TABLE_RANDOM_CONSTANT;
|
|
if ((LONG)hashValue < 0)
|
|
hashValue = (DWORD)-(LONG)hashValue;
|
|
hashValue %= TABLE_RANDOM_PRIME;
|
|
|
|
return hashValue;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
DWORD Table::BucketNum(DWORD hashValue) const
|
|
{
|
|
DWORD bucketNum;
|
|
|
|
ASSERT(expandIndex < 1U << level);
|
|
ASSERT(numBuckets == (1U << level) + expandIndex);
|
|
|
|
bucketNum = hashValue & ~(~0U << level);
|
|
if (bucketNum < expandIndex)
|
|
bucketNum = hashValue & ~(~0U << (level+1));
|
|
|
|
ASSERT(bucketNum < numBuckets);
|
|
|
|
return bucketNum;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
VOID Table::Expand()
|
|
{
|
|
TableEntry **oldSlotAddr,
|
|
**newSlotAddr,
|
|
*oldChain,
|
|
*newChain,
|
|
*entry;
|
|
|
|
TableSegment **newDirectory,
|
|
*newSegment;
|
|
|
|
DWORD oldNewMask;
|
|
|
|
#if DBG
|
|
TableEntry *prevChain;
|
|
DWORD mask;
|
|
#endif
|
|
|
|
// Increase the directory size if necessary.
|
|
|
|
ASSERT(directory != NULL);
|
|
ASSERT(dirSize >= TABLE_SEGMENT_SIZE);
|
|
ASSERT(dirSize % TABLE_SEGMENT_SIZE == 0);
|
|
|
|
if (numBuckets >= dirSize * TABLE_SEGMENT_SIZE) {
|
|
newDirectory = new TableSegment * [dirSize + TABLE_DIR_SIZE];
|
|
ASSERT(newDirectory != NULL);
|
|
memcpy(newDirectory, directory, sizeof(TableSegment *) * dirSize);
|
|
memset(newDirectory+dirSize, 0, sizeof(TableSegment *) * TABLE_DIR_SIZE);
|
|
dirSize += TABLE_DIR_SIZE;
|
|
delete directory;
|
|
directory = newDirectory;
|
|
}
|
|
|
|
// Find the old bucket to be expanded.
|
|
|
|
ASSERT(expandIndex >> TABLE_SEGMENT_BITS < dirSize);
|
|
|
|
oldSlotAddr = &directory[expandIndex >> TABLE_SEGMENT_BITS]
|
|
->slot[expandIndex & TABLE_SEGMENT_MASK];
|
|
|
|
ASSERT(oldSlotAddr != NULL);
|
|
|
|
// Find the new bucket, and create a new segment if necessary.
|
|
|
|
ASSERT(numBuckets >> TABLE_SEGMENT_BITS < dirSize);
|
|
|
|
newSegment = directory[numBuckets >> TABLE_SEGMENT_BITS];
|
|
|
|
if (newSegment == NULL) {
|
|
newSegment = new TableSegment;
|
|
ASSERT(newSegment != NULL);
|
|
memset(newSegment, 0, sizeof(TableSegment));
|
|
directory[numBuckets >> TABLE_SEGMENT_BITS] = newSegment;
|
|
}
|
|
|
|
newSlotAddr = &newSegment->slot[numBuckets & TABLE_SEGMENT_MASK];
|
|
|
|
ASSERT(*newSlotAddr == NULL);
|
|
|
|
// Relocate entries from the old to the new bucket.
|
|
|
|
oldNewMask = 1U << level;
|
|
oldChain = NULL;
|
|
newChain = NULL;
|
|
entry = *oldSlotAddr;
|
|
|
|
#if DBG
|
|
prevChain = NULL;
|
|
mask = ~(~0U << (level+1));
|
|
#endif
|
|
|
|
while (entry != NULL) {
|
|
ASSERT((entry->hashValue & ~(~0U << level)) == expandIndex);
|
|
ASSERT( entry->prevChain == prevChain);
|
|
|
|
// This entry moves to the new bucket.
|
|
|
|
if ((entry->hashValue & oldNewMask) != 0) {
|
|
if (newChain == NULL) {
|
|
*newSlotAddr = entry;
|
|
entry->prevChain = NULL;
|
|
} else {
|
|
newChain->nextChain = entry;
|
|
entry ->prevChain = newChain;
|
|
}
|
|
|
|
newChain = entry;
|
|
|
|
ASSERT((entry->hashValue & mask) == numBuckets);
|
|
}
|
|
|
|
// This entry stays in the old bucket.
|
|
|
|
else {
|
|
if (oldChain == NULL) {
|
|
*oldSlotAddr = entry;
|
|
entry->prevChain = NULL;
|
|
} else {
|
|
oldChain->nextChain = entry;
|
|
entry ->prevChain = oldChain;
|
|
}
|
|
|
|
oldChain = entry;
|
|
|
|
ASSERT((entry->hashValue & mask) == expandIndex);
|
|
}
|
|
|
|
#if DBG
|
|
prevChain = entry;
|
|
#endif
|
|
entry = entry->nextChain;
|
|
}
|
|
|
|
// Finish off each bucket chain.
|
|
|
|
if (oldChain == NULL)
|
|
*oldSlotAddr = NULL;
|
|
else
|
|
oldChain->nextChain = NULL;
|
|
|
|
if (newChain == NULL)
|
|
*newSlotAddr = NULL;
|
|
else
|
|
newChain->nextChain = NULL;
|
|
|
|
// Adjust the expand index and level, and increment the number of buckets.
|
|
|
|
if (++expandIndex == 1U << level) {
|
|
level++;
|
|
expandIndex = 0;
|
|
}
|
|
numBuckets++;
|
|
|
|
ASSERT(expandIndex < 1U << level);
|
|
ASSERT(numBuckets == (1U << level) + expandIndex);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
VOID Table::Contract()
|
|
{
|
|
TableEntry **targetSlotAddr,
|
|
**victimSlotAddr,
|
|
*firstVictimEntry,
|
|
*prevChain,
|
|
*entry;
|
|
|
|
TableSegment **newDirectory;
|
|
|
|
#if DBG
|
|
DWORD mask;
|
|
#endif
|
|
|
|
// Adjust the expand index and level, and decrement the number of buckets.
|
|
|
|
ASSERT(expandIndex < 1U << level);
|
|
ASSERT(numBuckets == (1U << level) + expandIndex);
|
|
|
|
if (expandIndex > 0)
|
|
expandIndex--;
|
|
else
|
|
expandIndex = (1U << --level) - 1;
|
|
numBuckets--;
|
|
|
|
ASSERT(expandIndex < 1U << level);
|
|
ASSERT(numBuckets == (1U << level) + expandIndex);
|
|
|
|
// Find the target and victim buckets.
|
|
|
|
ASSERT(directory != NULL);
|
|
ASSERT(dirSize >= TABLE_SEGMENT_SIZE);
|
|
ASSERT(dirSize % TABLE_SEGMENT_SIZE == 0);
|
|
|
|
targetSlotAddr = &directory[expandIndex >> TABLE_SEGMENT_BITS]
|
|
->slot[expandIndex & TABLE_SEGMENT_MASK];
|
|
victimSlotAddr = &directory[numBuckets >> TABLE_SEGMENT_BITS]
|
|
->slot[numBuckets & TABLE_SEGMENT_MASK];
|
|
|
|
ASSERT(targetSlotAddr != NULL);
|
|
ASSERT(victimSlotAddr != NULL);
|
|
|
|
// If the victim buffer isn't empty, ...
|
|
|
|
if ((firstVictimEntry = *victimSlotAddr) != NULL) {
|
|
#if DBG
|
|
mask = ~(~0U << (level+1));
|
|
#endif
|
|
ASSERT((firstVictimEntry->hashValue & mask) == numBuckets);
|
|
ASSERT( firstVictimEntry->prevChain == NULL);
|
|
|
|
// ... find the end of the target bucket chain, ...
|
|
|
|
entry = *targetSlotAddr;
|
|
prevChain = NULL;
|
|
|
|
while (entry != NULL) {
|
|
ASSERT((entry->hashValue & mask) == expandIndex);
|
|
ASSERT( entry->prevChain == prevChain);
|
|
|
|
prevChain = entry;
|
|
entry = entry->nextChain;
|
|
}
|
|
|
|
// ... then add the victim bucket chain to the end of the target bucket chain.
|
|
|
|
if (prevChain == NULL)
|
|
*targetSlotAddr = firstVictimEntry;
|
|
else {
|
|
prevChain->nextChain = firstVictimEntry;
|
|
firstVictimEntry->prevChain = prevChain;
|
|
}
|
|
}
|
|
|
|
// Delete the victim bucket, and delete the victim segment if no buckets remain.
|
|
|
|
if ((numBuckets & TABLE_SEGMENT_MASK) == 0) {
|
|
delete directory[numBuckets >> TABLE_SEGMENT_BITS];
|
|
directory[numBuckets >> TABLE_SEGMENT_BITS] = NULL;
|
|
} else
|
|
*victimSlotAddr = NULL;
|
|
|
|
// Reduce the size of the directory if necessary.
|
|
|
|
if (numBuckets <= (dirSize - TABLE_DIR_SIZE) * TABLE_SEGMENT_SIZE
|
|
&& dirSize > TABLE_DIR_SIZE) {
|
|
dirSize -= TABLE_DIR_SIZE;
|
|
newDirectory = new TableSegment * [dirSize];
|
|
ASSERT(newDirectory != NULL);
|
|
memcpy(newDirectory, directory, sizeof(TableSegment *) * dirSize);
|
|
delete directory;
|
|
directory = newDirectory;
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/************************ Table class public methods *************************/
|
|
/*****************************************************************************/
|
|
|
|
Table::Table()
|
|
{
|
|
firstEntry = NULL;
|
|
lastEntry = NULL;
|
|
|
|
numEntries = 0;
|
|
numBuckets = TABLE_SEGMENT_SIZE;
|
|
expandIndex = 0;
|
|
level = TABLE_SEGMENT_BITS;
|
|
|
|
dirSize = TABLE_DIR_SIZE;
|
|
directory = new TableSegment * [dirSize];
|
|
ASSERT(directory != NULL);
|
|
memset(directory, 0, sizeof(TableSegment *) * dirSize);
|
|
|
|
directory[0] = new TableSegment;
|
|
ASSERT(directory[0] != NULL);
|
|
memset(directory[0], 0, sizeof(TableSegment));
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
Table::~Table()
|
|
{
|
|
TableEntry *entry,
|
|
*prevEntry;
|
|
|
|
DWORD numSegments,
|
|
segmentNum,
|
|
count;
|
|
|
|
entry = firstEntry;
|
|
prevEntry = NULL;
|
|
count = 0;
|
|
|
|
while (entry != NULL) {
|
|
ASSERT(entry->prevEntry == prevEntry);
|
|
prevEntry = entry;
|
|
entry = entry->nextEntry;
|
|
delete prevEntry->data;
|
|
delete prevEntry;
|
|
count++;
|
|
}
|
|
ASSERT(count == numEntries);
|
|
|
|
numSegments = numBuckets >> TABLE_SEGMENT_BITS;
|
|
|
|
ASSERT(directory != NULL);
|
|
ASSERT(dirSize >= TABLE_SEGMENT_SIZE);
|
|
ASSERT(dirSize % TABLE_SEGMENT_SIZE == 0);
|
|
ASSERT(numSegments <= dirSize);
|
|
|
|
for (segmentNum = 0; segmentNum < numSegments; segmentNum++) {
|
|
ASSERT(directory[segmentNum] != NULL);
|
|
delete directory[segmentNum];
|
|
}
|
|
|
|
delete directory;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
BOOL Table::Put(
|
|
VOID *data,
|
|
DWORD keyLen)
|
|
{
|
|
TableEntry **slotAddr,
|
|
*prevChain,
|
|
*entry;
|
|
|
|
DWORD hashValue,
|
|
bucketNum;
|
|
|
|
#if DBG
|
|
DWORD mask;
|
|
#endif
|
|
|
|
ASSERT(data != NULL);
|
|
ASSERT(keyLen > 0);
|
|
|
|
// Find the bucket for this data.
|
|
|
|
hashValue = Hash(data, keyLen);
|
|
bucketNum = BucketNum(hashValue);
|
|
|
|
#if DBG
|
|
mask = ~(~0U << (bucketNum < expandIndex || bucketNum >= 1U << level
|
|
? level+1 : level));
|
|
#endif
|
|
|
|
ASSERT(directory != NULL);
|
|
|
|
slotAddr = &directory[bucketNum >> TABLE_SEGMENT_BITS]
|
|
->slot[bucketNum & TABLE_SEGMENT_MASK];
|
|
|
|
ASSERT(slotAddr != NULL);
|
|
|
|
entry = *slotAddr;
|
|
prevChain = NULL;
|
|
|
|
// Look at each entry in the bucket to determine if the data is
|
|
// already present. If a matching entry is found, return FALSE.
|
|
|
|
while (entry != NULL) {
|
|
ASSERT((entry->hashValue & mask) == bucketNum);
|
|
ASSERT( entry->prevChain == prevChain);
|
|
|
|
if (hashValue == entry->hashValue
|
|
&& keyLen == entry->keyLen
|
|
&& memcmp(data, entry->data, keyLen) == 0)
|
|
return FALSE;
|
|
|
|
prevChain = entry;
|
|
entry = entry->nextChain;
|
|
}
|
|
|
|
// No entry with matching data was found in this bucket.
|
|
// Create a new entry and add it to the end of the bucket chain.
|
|
|
|
entry = new TableEntry;
|
|
ASSERT(entry != NULL);
|
|
|
|
if (prevChain == NULL) {
|
|
*slotAddr = entry;
|
|
entry->prevChain = NULL;
|
|
} else {
|
|
prevChain->nextChain = entry;
|
|
entry ->prevChain = prevChain;
|
|
}
|
|
entry->nextChain = NULL;
|
|
|
|
// Add the entry to the end of the doubly-linked list.
|
|
|
|
if (lastEntry == NULL) {
|
|
ASSERT(firstEntry == NULL);
|
|
ASSERT(numEntries == 0);
|
|
firstEntry = entry;
|
|
entry->prevEntry = NULL;
|
|
} else {
|
|
ASSERT(firstEntry != NULL);
|
|
ASSERT(numEntries > 0);
|
|
lastEntry->nextEntry = entry;
|
|
entry ->prevEntry = lastEntry;
|
|
}
|
|
|
|
entry->nextEntry = NULL;
|
|
lastEntry = entry;
|
|
numEntries++;
|
|
|
|
// Fill out the entry.
|
|
|
|
entry->hashValue = hashValue;
|
|
entry->keyLen = keyLen;
|
|
entry->data = data;
|
|
|
|
// Expand the table if necessary.
|
|
|
|
if (numEntries > numBuckets * TABLE_MAX_LOAD) {
|
|
Expand();
|
|
ASSERT(numEntries <= numBuckets * TABLE_MAX_LOAD);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
VOID *Table::Get(
|
|
const VOID *key,
|
|
DWORD keyLen,
|
|
BOOL erase)
|
|
{
|
|
TableEntry **slotAddr,
|
|
*entry,
|
|
*prevChain;
|
|
|
|
DWORD hashValue,
|
|
bucketNum;
|
|
|
|
VOID *dataPtr;
|
|
|
|
#if DBG
|
|
DWORD mask;
|
|
#endif
|
|
|
|
ASSERT(key != NULL);
|
|
ASSERT(keyLen > 0);
|
|
|
|
// Find the bucket for this data.
|
|
|
|
hashValue = Hash(key, keyLen);
|
|
bucketNum = BucketNum(hashValue);
|
|
|
|
#if DBG
|
|
mask = ~(~0U << (bucketNum < expandIndex || bucketNum >= 1U << level
|
|
? level+1 : level));
|
|
#endif
|
|
|
|
ASSERT(directory != NULL);
|
|
|
|
slotAddr = &directory[bucketNum >> TABLE_SEGMENT_BITS]
|
|
->slot[bucketNum & TABLE_SEGMENT_MASK];
|
|
|
|
ASSERT(slotAddr != NULL);
|
|
|
|
entry = *slotAddr;
|
|
prevChain = NULL;
|
|
|
|
// Look at each entry in the bucket.
|
|
|
|
while (entry != NULL) {
|
|
ASSERT((entry->hashValue & mask) == bucketNum);
|
|
ASSERT( entry->prevChain == prevChain);
|
|
|
|
if (hashValue == entry->hashValue
|
|
&& keyLen == entry->keyLen
|
|
&& memcmp(key, entry->data, keyLen) == 0) {
|
|
|
|
// The entry with matching data has been found.
|
|
|
|
dataPtr = entry->data;
|
|
ASSERT(dataPtr != NULL);
|
|
|
|
// If erasure is disabled, remove the entry from the doubly-linked list ...
|
|
|
|
if (erase) {
|
|
if (entry->prevEntry == NULL) {
|
|
ASSERT(firstEntry == entry);
|
|
firstEntry = entry->nextEntry;
|
|
} else
|
|
entry->prevEntry->nextEntry = entry->nextEntry;
|
|
|
|
if (entry->nextEntry == NULL) {
|
|
ASSERT(lastEntry == entry);
|
|
lastEntry = entry->prevEntry;
|
|
} else
|
|
entry->nextEntry->prevEntry = entry->prevEntry;
|
|
|
|
// ... and from the bucket chain, ...
|
|
|
|
if (prevChain == NULL)
|
|
*slotAddr = entry->nextChain;
|
|
else
|
|
prevChain->nextChain = entry->nextChain;
|
|
|
|
if (entry->nextChain != NULL) {
|
|
ASSERT(entry->nextChain->prevChain == entry);
|
|
entry->nextChain->prevChain = prevChain;
|
|
}
|
|
|
|
// ... then delete the entry.
|
|
|
|
delete entry;
|
|
|
|
// Decrement the number of entries, and contract the table if necessary.
|
|
|
|
numEntries--;
|
|
if (numBuckets > TABLE_SEGMENT_SIZE
|
|
&& numEntries < numBuckets * TABLE_MIN_LOAD) {
|
|
Contract();
|
|
ASSERT(numBuckets <= TABLE_SEGMENT_SIZE
|
|
|| numEntries >= numBuckets * TABLE_MIN_LOAD);
|
|
}
|
|
}
|
|
|
|
return dataPtr;
|
|
}
|
|
|
|
// No entry with matching data has yet been found.
|
|
// Continue following the bucket chain.
|
|
|
|
prevChain = entry;
|
|
entry = entry->nextChain;
|
|
}
|
|
|
|
// No entry with matching data was found in this bucket.
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
VOID *Table::GetFirst(
|
|
DWORD *keyLen,
|
|
BOOL erase)
|
|
{
|
|
TableEntry **slotAddr,
|
|
*entry;
|
|
|
|
DWORD bucketNum;
|
|
|
|
VOID *dataPtr;
|
|
|
|
// If the table is empty, then simply return.
|
|
|
|
if (firstEntry == NULL) {
|
|
ASSERT(lastEntry == NULL);
|
|
ASSERT(numEntries == 0);
|
|
return NULL;
|
|
}
|
|
|
|
dataPtr = firstEntry->data;
|
|
ASSERT(dataPtr != NULL);
|
|
if (keyLen != NULL) {
|
|
*keyLen = firstEntry->keyLen;
|
|
ASSERT(firstEntry->keyLen > 0);
|
|
}
|
|
|
|
// If erasure is enabled, remove the first entry from the doubly-linked list ...
|
|
|
|
if (erase) {
|
|
entry = firstEntry;
|
|
firstEntry = entry->nextEntry;
|
|
|
|
if (firstEntry == NULL) {
|
|
ASSERT(numEntries == 1);
|
|
ASSERT(lastEntry == entry);
|
|
lastEntry = NULL;
|
|
} else {
|
|
ASSERT(numEntries > 1);
|
|
ASSERT(firstEntry->prevEntry == entry);
|
|
firstEntry->prevEntry = NULL;
|
|
}
|
|
|
|
// ... and from the bucket chain, ...
|
|
|
|
if (entry->prevChain == NULL) {
|
|
bucketNum = BucketNum(entry->hashValue);
|
|
ASSERT(directory != NULL);
|
|
slotAddr = &directory[bucketNum >> TABLE_SEGMENT_BITS]
|
|
->slot[bucketNum & TABLE_SEGMENT_MASK];
|
|
ASSERT( slotAddr != NULL);
|
|
ASSERT(*slotAddr == entry);
|
|
*slotAddr = entry->nextChain;
|
|
} else {
|
|
ASSERT(entry->prevChain->nextChain == entry);
|
|
entry->prevChain->nextChain = entry->nextChain;
|
|
}
|
|
|
|
if (entry->nextChain != NULL) {
|
|
ASSERT(entry->nextChain->prevChain == entry);
|
|
entry->nextChain->prevChain = entry->prevChain;
|
|
}
|
|
|
|
// ... then delete the entry.
|
|
|
|
delete entry;
|
|
|
|
// Decrement the number of entries, and contract the table if necessary.
|
|
|
|
numEntries--;
|
|
if (numBuckets > TABLE_SEGMENT_SIZE
|
|
&& numEntries < numBuckets * TABLE_MIN_LOAD) {
|
|
Contract();
|
|
ASSERT(numBuckets <= TABLE_SEGMENT_SIZE
|
|
|| numEntries >= numBuckets * TABLE_MIN_LOAD);
|
|
}
|
|
}
|
|
|
|
return dataPtr;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
DWORD Table::Number() const
|
|
{
|
|
return numEntries;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/************************* FIFO class public methods *************************/
|
|
/*****************************************************************************/
|
|
|
|
FIFO::FIFO()
|
|
{
|
|
head = tail = NULL;
|
|
numEntries = 0;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
FIFO::~FIFO()
|
|
{
|
|
FIFOEntry *entry = head,
|
|
*oldEntry;
|
|
|
|
DWORD count = 0;
|
|
|
|
while ((oldEntry = entry) != NULL) {
|
|
entry = entry->next;
|
|
delete oldEntry->data;
|
|
delete oldEntry;
|
|
count++;
|
|
}
|
|
|
|
ASSERT(count == numEntries);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
VOID FIFO::Put(VOID *data)
|
|
{
|
|
FIFOEntry *newEntry;
|
|
|
|
ASSERT(data != NULL);
|
|
|
|
newEntry = new FIFOEntry;
|
|
ASSERT(newEntry != NULL);
|
|
newEntry->next = NULL;
|
|
newEntry->data = data;
|
|
|
|
if (tail != NULL)
|
|
tail->next = newEntry;
|
|
else
|
|
head = newEntry;
|
|
tail = newEntry;
|
|
|
|
numEntries++;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
VOID *FIFO::Get()
|
|
{
|
|
FIFOEntry *oldHead;
|
|
|
|
VOID *dataPtr;
|
|
|
|
if (head == NULL) {
|
|
ASSERT(tail == NULL);
|
|
ASSERT(numEntries == 0);
|
|
return NULL;
|
|
}
|
|
|
|
ASSERT(tail != NULL);
|
|
ASSERT(numEntries > 0);
|
|
|
|
dataPtr = head->data;
|
|
|
|
oldHead = head;
|
|
head = head->next;
|
|
delete oldHead;
|
|
if (head == NULL)
|
|
tail = NULL;
|
|
numEntries--;
|
|
|
|
return dataPtr;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
DWORD FIFO::Number() const
|
|
{
|
|
return numEntries;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/************************* LIFO class public methods *************************/
|
|
/*****************************************************************************/
|
|
|
|
LIFO::LIFO()
|
|
{
|
|
top = NULL;
|
|
numEntries = 0;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
LIFO::~LIFO()
|
|
{
|
|
LIFOEntry *entry = top,
|
|
*oldEntry;
|
|
|
|
DWORD count = 0;
|
|
|
|
while ((oldEntry = entry) != NULL) {
|
|
entry = entry->next;
|
|
delete oldEntry->data;
|
|
delete oldEntry;
|
|
count++;
|
|
}
|
|
|
|
ASSERT(count == numEntries);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
VOID LIFO::Put(VOID *data)
|
|
{
|
|
LIFOEntry *newEntry;
|
|
|
|
ASSERT(data != NULL);
|
|
|
|
newEntry = new LIFOEntry;
|
|
ASSERT(newEntry != NULL);
|
|
newEntry->next = top;
|
|
newEntry->data = data;
|
|
top = newEntry;
|
|
numEntries++;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
VOID *LIFO::Get()
|
|
{
|
|
LIFOEntry *oldTop;
|
|
|
|
VOID *dataPtr;
|
|
|
|
if (top == NULL) {
|
|
ASSERT(numEntries == 0);
|
|
return NULL;
|
|
}
|
|
|
|
ASSERT(numEntries > 0);
|
|
|
|
dataPtr = top->data;
|
|
|
|
oldTop = top;
|
|
top = top->next;
|
|
delete oldTop;
|
|
numEntries--;
|
|
|
|
return dataPtr;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
DWORD LIFO::Number() const
|
|
{
|
|
return numEntries;
|
|
}
|