/*++ Copyright (c) 1997-1999 Microsoft Corporation Module Name: genhash.c Abstract: Generic Hash Table routines. Each hash table is an array of FRS_LIST entries that provide interlocked access to each row of the hash table. Each table is managed by a GENERIC_HASH_TABLE struct which holds function entry points for freeing an entry, comparing keys, performing a hash calculation, printing an entry and dumping the table. Note: All hash entries must be prefixed with GENERIC_HASH_ROW_ENTRY at the beginning of the structure. Author: David Orbits [davidor] 22-Apr-1997 Environment: User Mode Service Revision History: --*/ #include #pragma hdrstop #include #include #include #pragma warning( disable:4102) // unreferenced label PGENERIC_HASH_TABLE GhtCreateTable( PCHAR ArgName, ULONG NumberRows, ULONG KeyOffset, ULONG KeyLength, PGENERIC_HASH_FREE_ROUTINE GhtFree, PGENERIC_HASH_COMPARE_ROUTINE GhtCompare, PGENERIC_HASH_CALC_ROUTINE GhtHashCalc, PGENERIC_HASH_PRINT_ROUTINE GhtPrint ) /*++ Routine Description: Allocate and initialize a hash table. Arguments: ArgName -- The table name. [16 byte max] NumberRows -- The number of rows in the table. KeyOffset -- The byte offset to the key value in each table entry. KeyLength -- The byte length of the key value in each table entry. GhtFree -- function to call to free an entry. GhtCompare -- function to comoare two keys. GhtHashCalc -- function to calculate the ULONG hash value on a key. GhtPrint -- Function to print out a table entry. GhtDump -- Function to call to dump all table entries. Return Value: ptr to a GENERIC_HASH_TABLE struct or NULL if failure. use GetLastError for the error status. --*/ { #undef DEBSUB #define DEBSUB "GhtCreateTable:" PGENERIC_HASH_TABLE HashTable; PGENERIC_HASH_ROW_ENTRY RowBase, HashRowEntry; ULONG NameLen; ULONG WStatus; ULONG i; HashTable = (PGENERIC_HASH_TABLE) FrsAllocType(GENERIC_HASH_TABLE_TYPE); RowBase = (PGENERIC_HASH_ROW_ENTRY) FrsAlloc( NumberRows * sizeof(GENERIC_HASH_ROW_ENTRY)); NameLen = min(strlen(ArgName), 15); CopyMemory(HashTable->Name, ArgName, NameLen); HashTable->Name[NameLen] = '\0'; HashTable->NumberRows = NumberRows; HashTable->GhtFree = GhtFree; HashTable->GhtCompare = GhtCompare; HashTable->GhtHashCalc = GhtHashCalc; HashTable->GhtPrint = GhtPrint; HashTable->KeyOffset = KeyOffset; HashTable->KeyLength = KeyLength; HashTable->RowLockEnabled = TRUE; HashTable->RefCountEnabled = TRUE; HashTable->HeapHandle = NULL; HashTable->UseOffsets = FALSE; HashTable->OffsetBase = 0; HashTable->HashRowBase = RowBase; HashTable->LockTimeout = 10000; // milliseconds // // Initialize all the hash table rows. Each has a critical section and // an event to wait on. // HashRowEntry = RowBase; for (i=0; iEvent = CreateEvent(NULL, TRUE, FALSE, NULL); WStatus = FrsRtlInitializeList(&HashRowEntry->HashRow); if (WStatus != ERROR_SUCCESS) { goto CLEANUP; } //if (HashRowEntry->Event == NULL) { // WStatus = GetLastError(); // goto CLEANUP; //} HashRowEntry += 1; } return HashTable; CLEANUP: HashTable->NumberRows = i+1; GhtDestroyTable(HashTable); SetLastError(WStatus); return NULL; } VOID GhtDestroyTable( PGENERIC_HASH_TABLE HashTable ) /*++ Routine Description: Free all the memory for a hash table. This includes any data elements left in the table. No locks are acquired. Arguments: HashTable -- ptr to a GENERIC_HASH_TABLE struct. Return Value: None. --*/ { #undef DEBSUB #define DEBSUB "GhtDestroyTable:" PGENERIC_HASH_ROW_ENTRY RowEntry; ULONG i; if (HashTable == NULL) { return; } RowEntry = HashTable->HashRowBase; DPRINT1(5, "GhtDestroyTable for %s\n", HashTable->Name); // // Loop through all the Hash table rows and delete any entries still on // each row. // for (i=0; iNumberRows; i++, RowEntry++) { if (RowEntry->HashRow.Count != 0) { //DPRINT2(5, "HashRow: %d, RowCount %d\n",i, RowEntry->HashRow.Count); } ForEachListEntryLock(&RowEntry->HashRow, GENERIC_HASH_ENTRY_HEADER, ListEntry, FrsRtlRemoveEntryListLock(&RowEntry->HashRow, &pE->ListEntry); //DPRINT4(5, " Deleteing entry: %08x, Hval %08x, Index %d, refcnt %d\n", // pE, pE->HashValue, i, pE->ReferenceCount); (HashTable->GhtFree)(HashTable, pE); ); FrsRtlDeleteList(&RowEntry->HashRow); //if (RowEntry->Event != NULL) { // FRS_CLOSE(RowEntry->Event); //} } FrsFree(HashTable->HashRowBase); FrsFreeType(HashTable); return; } ULONG GhtCleanTableByFilter( PGENERIC_HASH_TABLE HashTable, IN PGENERIC_HASH_ENUM_ROUTINE Function, PVOID Context ) /*++ Routine Description: Free the elements in the hash table for which the predicate function returns TRUE. Arguments: HashTable -- ptr to a GENERIC_HASH_TABLE struct. Function - The function to call for each record in the table. It is of of type PGENERIC_HASH_FILTER_ROUTINE. Return TRUE to delete the entry in the table. Context -- Arg to pass thru to the filter function. Return Value: The number of entries deleted. --*/ { #undef DEBSUB #define DEBSUB "GhtCleanTableByFilter:" PGENERIC_HASH_ROW_ENTRY RowEntry; ULONG i; ULONG Count = 0; if (HashTable == NULL) { return Count; } RowEntry = HashTable->HashRowBase; // // Loop through all the Hash table rows and delete any entries still on // each row. // for (i=0; iNumberRows; i++, RowEntry++) { if (RowEntry->HashRow.Count != 0) { //DPRINT2(4, "HashRow: %d, RowCount %d\n", i, RowEntry->HashRow.Count); } ForEachListEntry(&RowEntry->HashRow, GENERIC_HASH_ENTRY_HEADER, ListEntry, // // The iterator pE is of type GENERIC_HASH_ENTRY_HEADER. // Call predicate to see if we should do the delete. // if ((Function)(HashTable, pE, Context)) { FrsRtlRemoveEntryListLock(&RowEntry->HashRow, &pE->ListEntry); //DPRINT4(4, "Deleting entry: %08x, Hval %08x, Index %d, refcnt %d\n", // pE, pE->HashValue, i, pE->ReferenceCount); (HashTable->GhtFree)(HashTable, pE); Count += 1; } ); } return Count; } #if DBG VOID GhtDumpTable( ULONG Sev, PGENERIC_HASH_TABLE HashTable ) /*++ Routine Description: Call the print routine for each element in the table. Arguments: Sev -- DPRINT severity level. HashTable -- ptr to a GENERIC_HASH_TABLE struct. Return Value: None. --*/ { #undef DEBSUB #define DEBSUB "GhtDumpTable:" PGENERIC_HASH_ROW_ENTRY HashRowEntry; ULONG i; HashRowEntry = HashTable->HashRowBase; DPRINT(Sev,"----------------------------------------------\n"); DPRINT(Sev,"----------------------------------------------\n"); DPRINT1(Sev, "GhtDumpTable for %s\n", HashTable->Name); DPRINT(Sev,"----------------------------------------------\n"); DPRINT(Sev,"----------------------------------------------\n"); // // Loop through all the Hash table rows and call the print function for // each element. // for (i=0; iNumberRows; i++) { if (HashRowEntry->HashRow.Count != 0) { DPRINT(Sev, " \n"); DPRINT(Sev,"----------------------------------------------\n"); DPRINT2(Sev, "HashRow: %d, RowCount %d\n", i, HashRowEntry->HashRow.Count); DPRINT5(Sev, "Inserts: %d, Removes: %d, Compares: %d, Lookups: %d, Lookupfails: %d \n", HashRowEntry->RowInserts, HashRowEntry->RowRemoves, HashRowEntry->RowCompares, HashRowEntry->RowLookups, HashRowEntry->RowLookupFails); } ForEachListEntry(&HashRowEntry->HashRow, GENERIC_HASH_ENTRY_HEADER, ListEntry, (HashTable->GhtPrint)(HashTable, pE); ); HashRowEntry += 1; } } #endif DBG ULONG_PTR GhtEnumerateTable( IN PGENERIC_HASH_TABLE HashTable, IN PGENERIC_HASH_ENUM_ROUTINE Function, IN PVOID Context ) /*++ Routine Description: This routine walks through the entries in a generic hash table and calls the function provided with the entry address and the context. No locks are taken by this function so the called function can make calls to other GHT routines to lookup or insert new entries. THis routine increments the ref count on each entry before it makes the call to ensure the entry does not vanish. It keeps a pointer to the entry that tells it where to continue the scan. If the argument function inserts an entry that is earlier in the table the enumeration will not pick it up. Arguments: HashTable - The context of the Hash Table to enumerate. Function - The function to call for each record in the table. It is of of type PGENERIC_HASH_ENUM_ROUTINE. Return FALSE to abort the enumeration else true. Context - A context ptr to pass through to the RecordFunction. Return Value: The status code from the argument function. --*/ { #undef DEBSUB #define DEBSUB "GhtEnumerateTable:" PGENERIC_HASH_ROW_ENTRY RowEntry; ULONG i; ULONG_PTR WStatus; RowEntry = HashTable->HashRowBase; DPRINT(5,"----------------------------------------------\n"); DPRINT(5,"----------------------------------------------\n"); DPRINT1(5, "GhtEnumerateTable for %s\n", HashTable->Name); DPRINT(5,"----------------------------------------------\n"); DPRINT(5,"----------------------------------------------\n"); // // Loop through all the Hash table rows and call the print function for // each element. // for (i=0; iNumberRows; i++) { if (RowEntry->HashRow.Count != 0) { DPRINT(5, " \n"); DPRINT(5,"----------------------------------------------\n"); DPRINT2(5, "HashRow: %d, RowCount %d\n", i, RowEntry->HashRow.Count); } ForEachListEntryLock(&RowEntry->HashRow, GENERIC_HASH_ENTRY_HEADER, ListEntry, InterlockedIncrement(&pE->ReferenceCount); DPRINT2(5, "inc ref: %08x, %d\n", pE, pE->ReferenceCount); WStatus = (Function)(HashTable, pE, Context); InterlockedDecrement(&pE->ReferenceCount); DPRINT2(5, "dec ref: %08x, %d\n", pE, pE->ReferenceCount); // Note: If caller needs this we should add code to check for // zero ref count and call the delete function. if( WStatus != 0 ) { return WStatus; } ); RowEntry += 1; } return ERROR_SUCCESS; } ULONG_PTR GhtEnumerateTableNoRef( IN PGENERIC_HASH_TABLE HashTable, IN PGENERIC_HASH_ENUM_ROUTINE Function, IN PVOID Context ) /*++ Routine Description: This routine walks through the entries in a generic hash table and calls the function provided with the entry address and the context. No locks are taken by this function so the called function can make calls to other GHT routines to lookup or insert new entries. THis routine does not take a ref count out on the entry. It keeps a pointer to the next entry that tells it where to continue the scan if the argument function deletes the entry. If the argument function inserts an entry that is earlier in the table the enumeration will not pick it up. Arguments: HashTable - The context of the Hash Table to enumerate. Function - The function to call for each record in the table. It is of of type PGENERIC_HASH_ENUM_ROUTINE. Return FALSE to abort the enumeration else true. Context - A context ptr to pass through to the RecordFunction. Return Value: The status code from the argument function. --*/ { #undef DEBSUB #define DEBSUB "GhtEnumerateTableNoRef:" PGENERIC_HASH_ROW_ENTRY RowEntry; ULONG i; ULONG_PTR WStatus; RowEntry = HashTable->HashRowBase; DPRINT1(5, "GhtEnumerateTableNoRef for %s\n", HashTable->Name); // // Loop through all the Hash table rows and call the print function for // each element. // for (i=0; iNumberRows; i++) { if (RowEntry->HashRow.Count != 0) { DPRINT(5, " \n"); DPRINT(5,"----------------------------------------------\n"); DPRINT2(5, "HashRow: %d, RowCount %d\n", i, RowEntry->HashRow.Count); } ForEachListEntryLock(&RowEntry->HashRow, GENERIC_HASH_ENTRY_HEADER, ListEntry, WStatus = (Function)(HashTable, pE, Context); if (WStatus != 0) { return WStatus; } ); RowEntry += 1; } return (ULONG_PTR)0; } PGENERIC_HASH_ENTRY_HEADER GhtGetNextEntry( IN PGENERIC_HASH_TABLE HashTable, PGENERIC_HASH_ENTRY_HEADER HashEntry ) /*++ Routine Description: This routine returns the next entry in the table that follows the HashEntry argument. If the HashEntry is NULL it returns the first entry. It gets the row lock containing the current entry, decrements the ref count on the entry. It scans forward to the next entry in the table getting its row lock if needed, increments its ref count and returns the pointer. If the end of table is reached NULL is returned. If an entry is inserted earlier in the table the enumeration will not pick it up. Arguments: HashTable - The context of the Hash Table to enumerate. HashEntry - The current entry we are looking at. Used to get the next entry If null start scan at beginning of table. Return Value: The status code from the argument function. --*/ { #undef DEBSUB #define DEBSUB "GhtGetNextEntry:" PGENERIC_HASH_ROW_ENTRY LastRow; ULONG Hval, HvalIndex; PGENERIC_HASH_ROW_ENTRY RowEntry; PLIST_ENTRY Entry; RowEntry = HashTable->HashRowBase; // // Get the hash value for the element and compute the index and RowEntry // address. Then get the row lock. // if (HashEntry != NULL) { Hval = HashEntry->HashValue; HvalIndex = Hval % HashTable->NumberRows; RowEntry += HvalIndex; // // Get the row lock and decrement the ref count. // (could delete if it hits zero) // FrsRtlAcquireListLock(&RowEntry->HashRow); InterlockedDecrement(&HashEntry->ReferenceCount); // // look for next entry in same row. // if found, bump ref count, drop lock, return entry. // Entry = GetListNext(&HashEntry->ListEntry); if (Entry != &RowEntry->HashRow.ListHead) { goto FOUND; } // // if not found drop row lock and execute scan code below // starting from next row entry. // FrsRtlReleaseListLock(&RowEntry->HashRow); RowEntry += 1; } // // Scan the rest of the table for a non-empty row. // LastRow = HashTable->HashRowBase + HashTable->NumberRows; while (RowEntry < LastRow) { if (RowEntry->HashRow.Count != 0) { // // Found one. Get the row lock and recheck the count incase // someone beat us too it. // FrsRtlAcquireListLock(&RowEntry->HashRow); if (RowEntry->HashRow.Count == 0) { // // Too bad. Continue scan. // FrsRtlReleaseListLock(&RowEntry->HashRow); RowEntry += 1; continue; } // // We got one. Get the entry address, bump the ref count, drop lock. // FRS_ASSERT(!IsListEmpty(&RowEntry->HashRow.ListHead)); Entry = GetListHead(&RowEntry->HashRow.ListHead); goto FOUND; } RowEntry += 1; } return NULL; FOUND: HashEntry = CONTAINING_RECORD(Entry, GENERIC_HASH_ENTRY_HEADER, ListEntry); InterlockedIncrement(&HashEntry->ReferenceCount); FrsRtlReleaseListLock(&RowEntry->HashRow); return HashEntry; } ULONG GhtCountEntries( IN PGENERIC_HASH_TABLE HashTable ) /*++ Routine Description: This routine walks through the rows in a generic hash table and adds up the entry count. It takes no locks so the count is approx. Caller must know the table can't go away. Arguments: HashTable - The context of the Hash Table to count. Return Value: The count. --*/ { #undef DEBSUB #define DEBSUB "GhtCountEntries:" ULONG Total = 0; PGENERIC_HASH_ROW_ENTRY LastRow, RowEntry = HashTable->HashRowBase; // // Loop through all the Hash table rows and add counts. // LastRow = RowEntry + HashTable->NumberRows; while (RowEntry < LastRow) { Total += RowEntry->HashRow.Count; RowEntry += 1; } return Total; } PGENERIC_HASH_ENTRY_HEADER GhtGetEntryNumber( IN PGENERIC_HASH_TABLE HashTable, IN LONG EntryNumber ) /*++ Routine Description: This routine walks through the rows in a generic hash table counting entries as it goes. It returns the requested entry (by number) from the table. Note this will not be the same entry from call to call because of intervening inserts and deletes. It takes no locks until it gets to the row of the table that contains the entry. The ref count on the entry is incremented. Arguments: HashTable - The context of the Hash Table to enumerate. EntryNumber - The ordinal number of the entry in the table. zero is the first entry. Return Value: The address of the entry. --*/ { #undef DEBSUB #define DEBSUB "GhtGetEntryNumber:" PGENERIC_HASH_ROW_ENTRY LastRow, RowEntry = HashTable->HashRowBase; ULONG Rcount; PLIST_ENTRY Entry; PGENERIC_HASH_ENTRY_HEADER HashEntry; FRS_ASSERT(EntryNumber >= 0); // // Loop through Hash table rows looking for the row with the entry. // LastRow = RowEntry + HashTable->NumberRows; while (RowEntry < LastRow) { Rcount = RowEntry->HashRow.Count; if (Rcount > 0) { EntryNumber -= Rcount; if (EntryNumber < 0) { // // Should be in this row. Get the row lock and recheck // the count incase someone beat us too it. // FrsRtlAcquireListLock(&RowEntry->HashRow); if (RowEntry->HashRow.Count < Rcount) { // // Too bad. It got shorter, Retry test. // FrsRtlReleaseListLock(&RowEntry->HashRow); EntryNumber += Rcount; continue; } // // We got one. Get the entry address, bump the ref count, drop lock. // EntryNumber += Rcount; Entry = GetListHead(&RowEntry->HashRow.ListHead); while (EntryNumber-- > 0) { FRS_ASSERT(Entry != &RowEntry->HashRow.ListHead); Entry = GetListNext(Entry); } HashEntry = CONTAINING_RECORD(Entry, GENERIC_HASH_ENTRY_HEADER, ListEntry); InterlockedIncrement(&HashEntry->ReferenceCount); FrsRtlReleaseListLock(&RowEntry->HashRow); return HashEntry; } } RowEntry += 1; } return NULL; } PGENERIC_HASH_ENTRY_HEADER GhtQuickCheck( PGENERIC_HASH_TABLE HashTable, PGENERIC_HASH_ROW_ENTRY RowEntry, PGENERIC_HASH_ENTRY_HEADER HashEntry, ULONG Hval ) /*++ Routine Description: Internal function to do a quick scan of a row to find an entry. Used in debug code to check that an entry is actually in the table. Assumes caller has the row lock. Arguments: HashTable -- ptr to a GENERIC_HASH_TABLE struct. RowEntry -- ptr to the ROW_ENTRY struct. HashEntry -- ptr to the hash entry we are looking for. Hval -- hash value we are looking for. Return Value: ptr to entry if we find it. NULL if we don't. --*/ { PCHAR pKeyValue; pKeyValue = ((PCHAR)HashEntry) + HashTable->KeyOffset; ForEachListEntryLock(&RowEntry->HashRow, GENERIC_HASH_ENTRY_HEADER, ListEntry, // // The iterator pE is of type PGENERIC_HASH_ENTRY_HEADER. // if (pE->HashValue == Hval) { if ((HashTable->GhtCompare)(pKeyValue, ((PCHAR)pE) + HashTable->KeyOffset, HashTable->KeyLength)) { // // Found it. // return pE; } } ); return NULL; } GHT_STATUS GhtLookup2( PGENERIC_HASH_TABLE HashTable, PVOID pKeyValue, BOOL WaitIfLocked, PVOID *RetHashEntry, ULONG DupIndex ) /*++ Routine Description: Takes the KeyValue and calls the hash function which returns a ULONG. Then calculate the index of HashValue Mod TableLenth. With the index find the hash row header and acquire the row lock. It then walks the list looking for a hash value match on the KeyValue. The entires are kept in ascending order so the lookup stops as soon as new entry value is < the list entry value. Then call the compare routine to see if the key data in the entry (entry+key offset) matches the keyValue passed in. If it matches, the ref count in the entry is bumped and the address is returned. If there are duplicate entries then the ptr to the nth oldest duplicate is returned where n is supplied by DupIndex. A value of 0 for DupIndex means return the last duplicate in the list. This is the most recent duplicate inserted since insert puts new entries at the end of the duplicate group. A value of one returns the oldest duplicate as determined by time of insert. TBI - If the row is locked and WaitIfLocked is TRUE then we wait on the row event. If the row is locked and WaitIfLocked is FALSE then return status GHT_STATUS_LOCKCONFLICT. In this case you can't tell if the entry is in the table. Arguments: HashTable -- ptr to a GENERIC_HASH_TABLE struct. pKeyValue -- ptr to the keyValue we are looking for. WaitIfLocked -- True means wait if the row is locked. RetHashEntry -- Returned ptr if found or NULL. DupIndex -- return the nth duplicate, if 0 return last duplicate in list. Return Value: GHT_STATUS_NOT_FOUND -- if not found. GHT_STATUS_SUCCESS -- if found. --*/ { #undef DEBSUB #define DEBSUB "GhtLookup2:" ULONG GStatus; ULONG Hval, HvalIndex; PGENERIC_HASH_ROW_ENTRY RowEntry; PGENERIC_HASH_ENTRY_HEADER LastFoundpE = NULL; // Note: Get lock earlier if table resize support is added. if (pKeyValue == NULL) { *RetHashEntry = NULL; return GHT_STATUS_NOT_FOUND; } // // Compute the hash index and calculate the row pointer. // Hval = (HashTable->GhtHashCalc)(pKeyValue, HashTable->KeyLength); HvalIndex = Hval % HashTable->NumberRows; RowEntry = HashTable->HashRowBase + HvalIndex; if (FrsRtlCountList(&RowEntry->HashRow) == 0) { *RetHashEntry = NULL; RowEntry->RowLookupFails += 1; return GHT_STATUS_NOT_FOUND; } if (DupIndex == 0) { DupIndex = 0xFFFFFFFF; } FrsRtlAcquireListLock(&RowEntry->HashRow); // // Walk the list looking for a match on the // the hash value then try and match the KeyValue. // ForEachListEntryLock(&RowEntry->HashRow, GENERIC_HASH_ENTRY_HEADER, ListEntry, // // The iterator pE is of type PGENERIC_HASH_ENTRY_HEADER. // RowEntry->RowCompares += 1; if (Hval < pE->HashValue) { // // Not on the list. // break; } if (pE->HashValue == Hval) { if ((HashTable->GhtCompare)(pKeyValue, ((PCHAR)pE) + HashTable->KeyOffset, HashTable->KeyLength)) { // // Found it. Check DupIndex count. // RowEntry->RowLookups += 1; LastFoundpE = pE; if (--DupIndex == 0) { break; } } } ); if (LastFoundpE != NULL) { // // Found one. Bump ref count, release the lock, return success. // InterlockedIncrement(&LastFoundpE->ReferenceCount); DPRINT2(5, ":: inc ref: %08x, %d\n", LastFoundpE, LastFoundpE->ReferenceCount); GStatus = GHT_STATUS_SUCCESS; } else { RowEntry->RowLookupFails += 1; GStatus = GHT_STATUS_NOT_FOUND; } ReleaseListLock(&RowEntry->HashRow); *RetHashEntry = LastFoundpE; return GStatus; } GHT_STATUS GhtInsert( PGENERIC_HASH_TABLE HashTable, PVOID HashEntryArg, BOOL WaitIfLocked, BOOL DuplicatesOk ) /*++ Routine Description: Inserts a HashEntry into the HashTable. It calls the hash function with a ptr to the key data (HashEntry+key offset) which returns a ULONG that is stored in HashEntry.HashValue. Insert then calculates the index of HashValue Mod TableLenth. With the index it finds the hash row header and acquires the row lock. It then walks the list looking for a hash value match. The entires are kept in ascending order so the lookup stops as soon as new entry value is < the list entry value. It then inserts the entry in the table, updates the counts in the row header, releases the lock and returns. If it finds a match it calls the user compare function with HashEntry+offset and ListEntry+offset to validate the match. The validate returns true if it matches and false if it fails (i.e. continue walking the list). Duplicates are allowed when DuplicatesOk is True. Insert returns GHT_STATUS_SUCCESS if the entry was inserted and GHT_STATUS_FAILURE if this is a duplicate node and DuplicatesOk is False (the compare function returned TRUE). The refcount is incremented if the node was inserted. Note: All hash entries must be prefixed with GENERIC_HASH_ROW_ENTRY at the beginning of the structure. TBI - If the row is locked and WaitIfLocked is FALSE then return status GHT_STATUS_LOCKCONFLICT else wait on the row. Arguments: HashTable -- ptr to a GENERIC_HASH_TABLE struct. HashEntryArg -- ptr to new entry to insert. WaitIfLocked -- True means wait if the row is locked. DuplicatesOk -- True means duplicate entries are ok. They are placed at the end of the list of duplicates. Return Value: GHT_STATUS_FAILURE -- Conflicting entry is in table already. GHT_STATUS_SUCCESS -- Insert was successful. --*/ { #undef DEBSUB #define DEBSUB "GhtInsert:" ULONG Hval, HvalIndex; PGENERIC_HASH_ROW_ENTRY RowEntry; PVOID pKeyValue; PLIST_ENTRY BeforeEntry; PGENERIC_HASH_ENTRY_HEADER HashEntry = (PGENERIC_HASH_ENTRY_HEADER)HashEntryArg; // // Compute the hash value on the key in the entry. // pKeyValue = ((PCHAR)HashEntry) + HashTable->KeyOffset; Hval = (HashTable->GhtHashCalc)(pKeyValue, HashTable->KeyLength); HashEntry->HashValue = Hval; // // Compute the index and calculate the row pointer. // HvalIndex = Hval % HashTable->NumberRows; RowEntry = HashTable->HashRowBase + HvalIndex; FrsRtlAcquireListLock(&RowEntry->HashRow); BeforeEntry = &RowEntry->HashRow.ListHead; // incase of empty list. // // Walk the list with the lock looking for a match on the // the hash value then try and match the KeyValue. // ForEachListEntryLock(&RowEntry->HashRow, GENERIC_HASH_ENTRY_HEADER, ListEntry, RowEntry->RowCompares += 1; if (Hval < pE->HashValue) { // // Not on the list. Put new entry before this one. // BeforeEntry = &pE->ListEntry; break; } if (pE->HashValue == Hval) { if ((HashTable->GhtCompare)(pKeyValue, ((PCHAR)pE) + HashTable->KeyOffset, HashTable->KeyLength)) { // // Found it. Release the lock and return failure if no // duplicates are allowed. // if (!DuplicatesOk) { FrsRtlReleaseListLock(&RowEntry->HashRow); return GHT_STATUS_FAILURE; } } } ); // // Put new entry on the list in front of 'BeforeEntry'. // InterlockedIncrement(&HashEntry->ReferenceCount); DPRINT2(5, ":: inc ref: %08x, %d\n", HashEntry, HashEntry->ReferenceCount); RowEntry->RowInserts += 1; FrsRtlInsertBeforeEntryListLock( &RowEntry->HashRow, BeforeEntry, &HashEntry->ListEntry); FrsRtlReleaseListLock(&RowEntry->HashRow); return GHT_STATUS_SUCCESS; } GHT_STATUS GhtDeleteEntryByAddress( PGENERIC_HASH_TABLE HashTable, PVOID HashEntryArg, BOOL WaitIfLocked ) /*++ Routine Description: Takes HashEntry address and fetches the hash value to acquire the row lock. Decrement the reference count and if it is one (the count for being in the table) remove the entry from the row and call the memory free function to release the entries memory. Drop the row lock. Return GHT_STATUS_SUCCESS if we deleted the entry. TBI - Return GHT_STATUS_LOCKCONFLICT if we failed to get the lock and WaitIfLocked was FALSE. Note: This function is only safe if you have a reference on the entry otherwise another thread could have already deleted the entry and your entry address is pointing at freed memory. Arguments: HashTable -- ptr to a GENERIC_HASH_TABLE struct. HashEntryArg -- ptr to entry to delete. WaitIfLocked -- True means wait if the row is locked. Return Value: GHT_STATUS_FAILURE -- Entry was not deleted. GHT_STATUS_SUCCESS -- Entry was deleted. --*/ { #undef DEBSUB #define DEBSUB "GhtDeleteEntryByAddress:" ULONG Hval, HvalIndex; PGENERIC_HASH_ROW_ENTRY RowEntry; BOOL Found; ULONG GhtStatus; LONG NewCount; PGENERIC_HASH_ENTRY_HEADER HashEntry = (PGENERIC_HASH_ENTRY_HEADER)HashEntryArg; GhtStatus = GHT_STATUS_FAILURE; // // Get the hash value for the element and compute the index and RowEntry // address. Then get the row lock. // Hval = HashEntry->HashValue; HvalIndex = Hval % HashTable->NumberRows; RowEntry = HashTable->HashRowBase + HvalIndex; FrsRtlAcquireListLock(&RowEntry->HashRow); #if DBG // // check if the entry is actually on the List. // // Walk the list with the lock looking for a match on the // the hash value then try and match the KeyValue. // Found = GhtQuickCheck(HashTable, RowEntry, HashEntry, Hval) != NULL; if (!Found) { DPRINT4(0, "GhtDeleteEntryByAddress - entry not on list %08x, %08x, %d, %s\n", HashEntry, Hval, HvalIndex, HashTable->Name); FRS_ASSERT(!"entry not on list"); FrsRtlReleaseListLock(&RowEntry->HashRow); return GHT_STATUS_FAILURE; } #endif // // Decrement the ref count. // NewCount = InterlockedDecrement(&HashEntry->ReferenceCount); DPRINT2(5, ":: dec ref: %08x, %d\n", HashEntry, HashEntry->ReferenceCount); if (NewCount <= 0) { DPRINT4(0, "GhtDeleteEntryByAddress - ref count equal zero: %08x, %08x, %d, %s\n", HashEntry, Hval, HvalIndex, HashTable->Name); FRS_ASSERT(!"ref count <= zero"); FrsRtlReleaseListLock(&RowEntry->HashRow); return GHT_STATUS_FAILURE; } if (NewCount == 1) { // // Ref count zero. Remove and free the entry. // FrsRtlRemoveEntryListLock(&RowEntry->HashRow, &HashEntry->ListEntry); (HashTable->GhtFree)(HashTable, HashEntry); GhtStatus = GHT_STATUS_SUCCESS; } FrsRtlReleaseListLock(&RowEntry->HashRow); return GhtStatus; } GHT_STATUS GhtRemoveEntryByAddress( PGENERIC_HASH_TABLE HashTable, PVOID HashEntryArg, BOOL WaitIfLocked ) /*++ Routine Description: Takes HashEntry address and fetches the hash value to acquire the row lock. Remove the entry from the table. The reference count is decremented. Return GHT_STATUS_SUCCESS. TBI - Return GHT_STATUS_LOCKCONFLICT if we failed to get the lock and WaitIfLocked was FALSE. Note: This function is only safe if you have a reference on the entry otherwise another thread could have already deleted the entry and your entry address is pointing at freed memory. Also Note: The caller must have a lock that prevents other threads from changing the entry. In addition removing an entry from one hash table and inserting it on another will confuse another thread that may be accessing the entry so the caller better be sure that no other thread assumes the hash table can't change while it has a reference to the entry. Arguments: HashTable -- ptr to a GENERIC_HASH_TABLE struct. HashEntryArg -- ptr to entry to delete. WaitIfLocked -- True means wait if the row is locked. Return Value: GHT_STATUS_SUCCESS -- if the entry was removed successfully. GHT_STATUS_FAILURE -- if the entry was not on the list. --*/ { #undef DEBSUB #define DEBSUB "GhtRemoveEntryByAddress:" ULONG Hval, HvalIndex; PGENERIC_HASH_ROW_ENTRY RowEntry; BOOL Found; LONG NewCount; PGENERIC_HASH_ENTRY_HEADER HashEntry = (PGENERIC_HASH_ENTRY_HEADER)HashEntryArg; // // Get the hash value for the element and compute the index and RowEntry // address. Then get the row lock. // Hval = HashEntry->HashValue; HvalIndex = Hval % HashTable->NumberRows; RowEntry = HashTable->HashRowBase + HvalIndex; FrsRtlAcquireListLock(&RowEntry->HashRow); #if DBG // // check if the entry is actually on the List. // // Walk the list with the lock looking for a match on the // the hash value then try and match the KeyValue. // Found = GhtQuickCheck(HashTable, RowEntry, HashEntry, Hval) != NULL; if (!Found) { DPRINT4(0, "GhtRemoveEntryByAddress - entry not on list %08x, %08x, %d, %s\n", HashEntry, Hval, HvalIndex, HashTable->Name); FRS_ASSERT(!"entry not on list-2"); FrsRtlReleaseListLock(&RowEntry->HashRow); return GHT_STATUS_FAILURE; } #endif // // Decrement the ref count. // NewCount = InterlockedDecrement(&HashEntry->ReferenceCount); DPRINT2(5, ":: dec ref: %08x, %d\n", HashEntry, HashEntry->ReferenceCount); if (NewCount < 0) { DPRINT4(0, ":: ERROR- GhtRemoveEntryByAddress - ref count less than zero: %08x, %08x, %d, %s\n", HashEntry, Hval, HvalIndex, HashTable->Name); FRS_ASSERT(!"ref count less than zero-2"); FrsRtlReleaseListLock(&RowEntry->HashRow); return GHT_STATUS_FAILURE; } if (NewCount > 1) { // // Other Refs than the caller's exist. Print a warning. // DPRINT5(1, ":: WARNING- GhtRemoveEntryByAddress - ref count(%d) > 1: %08x, %08x, %d, %s\n", NewCount, HashEntry, Hval, HvalIndex, HashTable->Name); } FrsRtlRemoveEntryListLock(&RowEntry->HashRow, &HashEntry->ListEntry); FrsRtlReleaseListLock(&RowEntry->HashRow); return GHT_STATUS_SUCCESS; } GHT_STATUS GhtReferenceEntryByAddress( PGENERIC_HASH_TABLE HashTable, PVOID HashEntryArg, BOOL WaitIfLocked ) /*++ Routine Description: Takes HashEntry address and fetches the hash value to acquire the row lock. Increment the reference count. Drop the row lock. TBI - Return GHT_STATUS_LOCKCONFLICT if we failed to get the lock and WaitIfLocked was FALSE. Note: This function is only safe if you have a reference on the entry otherwise another thread could have already deleted the entry and your entry address is pointing at freed memory. A Lookup which gave you the address bumps the reference count. An insert in which you kept the address does NOT bump the reference count. Arguments: HashTable -- ptr to a GENERIC_HASH_TABLE struct. HashEntryArg -- ptr to entry to reference. WaitIfLocked -- True means wait if the row is locked. Return Value: GHT_STATUS_SUCCESS --*/ { #undef DEBSUB #define DEBSUB "GhtReferenceEntryByAddress:" ULONG Hval, HvalIndex; PGENERIC_HASH_ROW_ENTRY RowEntry; BOOL Found; PGENERIC_HASH_ENTRY_HEADER HashEntry = (PGENERIC_HASH_ENTRY_HEADER)HashEntryArg; // // Get the hash value for the element and compute the index and RowEntry // address. Then get the row lock. // Hval = HashEntry->HashValue; HvalIndex = Hval % HashTable->NumberRows; RowEntry = HashTable->HashRowBase + HvalIndex; FrsRtlAcquireListLock(&RowEntry->HashRow); #if DBG // // check if the entry is actually on the List. // // Walk the list with the lock looking for a match on the // the hash value then try and match the KeyValue. // Found = GhtQuickCheck(HashTable, RowEntry, HashEntry, Hval) != NULL; if (!Found) { DPRINT4(0, "GhtReferenceEntryByAddress - entry not on list %08x, %08x, %d, %s\n", HashEntry, Hval, HvalIndex, HashTable->Name); FRS_ASSERT(!"entry not on list-3"); FrsRtlReleaseListLock(&RowEntry->HashRow); return GHT_STATUS_FAILURE; } #endif // // Increment the ref count. // InterlockedIncrement(&HashEntry->ReferenceCount); DPRINT2(5, ":: inc ref: %08x, %d\n", HashEntry, HashEntry->ReferenceCount); FrsRtlReleaseListLock(&RowEntry->HashRow); return GHT_STATUS_SUCCESS; } GHT_STATUS GhtDereferenceEntryByAddress( PGENERIC_HASH_TABLE HashTable, PVOID HashEntryArg, BOOL WaitIfLocked ) /*++ Routine Description: Takes HashEntry address and fetches the hash value to acquire the row lock. Decrement the reference count. Drop the row lock. TBI - Return GHT_STATUS_LOCKCONFLICT if we failed to get the lock and WaitIfLocked was FALSE. Note: This function is only safe if you have a reference on the entry otherwise another thread could have already deleted the entry and your entry address is pointing at freed memory. A Lookup which gave you the address bumps the reference count. An insert in which you kept the address does NOT bump the reference count. Arguments: HashTable -- ptr to a GENERIC_HASH_TABLE struct. HashEntryArg -- ptr to entry to reference. WaitIfLocked -- True means wait if the row is locked. Return Value: GHT_STATUS_SUCCESS --*/ { #undef DEBSUB #define DEBSUB "GhtDereferenceEntryByAddress:" ULONG Hval, HvalIndex; PGENERIC_HASH_ROW_ENTRY RowEntry; BOOL Found; LONG NewCount; PGENERIC_HASH_ENTRY_HEADER HashEntry = (PGENERIC_HASH_ENTRY_HEADER)HashEntryArg; // // Get the hash value for the element and compute the index and RowEntry // address. Then get the row lock. // Hval = HashEntry->HashValue; HvalIndex = Hval % HashTable->NumberRows; RowEntry = HashTable->HashRowBase + HvalIndex; FrsRtlAcquireListLock(&RowEntry->HashRow); #if DBG // // check if the entry is actually on the List. // // Walk the list with the lock looking for a match on the // the hash value then try and match the KeyValue. // Found = GhtQuickCheck(HashTable, RowEntry, HashEntry, Hval) != NULL; if (!Found) { DPRINT4(0, "GhtDereferenceEntryByAddress - entry not on list %08x, %08x, %d, %s\n", HashEntry, Hval, HvalIndex, HashTable->Name); FRS_ASSERT(!"entry not on list-4"); FrsRtlReleaseListLock(&RowEntry->HashRow); return GHT_STATUS_FAILURE; } #endif // // Decrement the ref count. // NewCount = InterlockedDecrement(&HashEntry->ReferenceCount); DPRINT2(5, ":: dec ref: %08x, %d\n", HashEntry, HashEntry->ReferenceCount); if (NewCount <= 0) { DPRINT4(0, "GhtDereferenceEntryByAddress - ref count now zero: %08x, %08x, %d, %s\n", HashEntry, Hval, HvalIndex, HashTable->Name); FRS_ASSERT(!"ref count now zero-4"); FrsRtlReleaseListLock(&RowEntry->HashRow); return GHT_STATUS_FAILURE; } FrsRtlReleaseListLock(&RowEntry->HashRow); return GHT_STATUS_SUCCESS; } GHT_STATUS GhtAdjustRefCountByKey( PGENERIC_HASH_TABLE HashTable, PVOID pKeyValue, LONG Delta, ULONG ActionIfZero, BOOL WaitIfLocked, PVOID *RetHashEntry ) /*++ Routine Description: Takes keyvalue, finds the HashEntry address and adds Delta to the reference count. Drop the row lock. ** WARNING ** If you allow duplicate entries in the hash table this routine will not work because you can't guarantee that you will adjust a given entry. TBI - Return GHT_STATUS_LOCKCONFLICT if we failed to get the lock and WaitIfLocked was FALSE. Arguments: HashTable -- ptr to a GENERIC_HASH_TABLE struct. pKeyValue -- ptr to a datavalue for the key. Delta -- The amount of the ref count adjustment. ActionIfZero -- If RC is zero Choice of nothing, remove, remove and delete. WaitIfLocked -- True means wait if the row is locked. RetHashEntry -- If GHT_ACTION_REMOVE requested, the hash entry address is returned if element removed else NULL returned. Return Value: GHT_STATUS_SUCCESS GHT_STATUS_NOT_FOUND --*/ { #undef DEBSUB #define DEBSUB "GhtDecrementRefCountByKey:" ULONG Hval, HvalIndex; PGENERIC_HASH_ROW_ENTRY RowEntry; LONG NewCount; if (ActionIfZero == GHT_ACTION_REMOVE) { *RetHashEntry = NULL; } // Note: Get lock earlier if table resize support is added. // // Compute the hash index and calculate the row pointer. // Hval = (HashTable->GhtHashCalc)(pKeyValue, HashTable->KeyLength); HvalIndex = Hval % HashTable->NumberRows; RowEntry = HashTable->HashRowBase + HvalIndex; if (FrsRtlCountList(&RowEntry->HashRow) == 0) { RowEntry->RowLookupFails += 1; return GHT_STATUS_NOT_FOUND; } // // Walk the list with the lock looking for a match on the // the hash value then try and match the KeyValue. // ForEachListEntry(&RowEntry->HashRow, GENERIC_HASH_ENTRY_HEADER, ListEntry, // // pE is iterator of type GENERIC_HASH_ENTRY_HEADER. // RowEntry->RowCompares += 1; if (pE->HashValue == Hval) { if ((HashTable->GhtCompare)(pKeyValue, ((PCHAR)pE) + HashTable->KeyOffset, HashTable->KeyLength)) { // // Found it. Adjust ref count, // NewCount = InterlockedExchangeAdd(&pE->ReferenceCount, Delta); DPRINT2(5, ":: adj ref: %08x, %d\n", pE, pE->ReferenceCount); RowEntry->RowLookups += 1; if (NewCount <= 0) { if (NewCount < 0) { DPRINT4(0, "GhtDecrementRefCountByKey - ref count neg: %08x, %08x, %d, %s\n", pE, Hval, HvalIndex, HashTable->Name); FRS_ASSERT(!"ref count neg-5"); ReleaseListLock(&RowEntry->HashRow); return GHT_STATUS_FAILURE; } // // Ref count zero. Optionally remove and free the entry. // if (ActionIfZero == GHT_ACTION_REMOVE) { *RetHashEntry = pE; FrsRtlRemoveEntryListLock(&RowEntry->HashRow, &pE->ListEntry); } else if (ActionIfZero == GHT_ACTION_DELETE) { (HashTable->GhtFree)(HashTable, pE); } else { // // Not good. Action was noop so refcount expected to // be > 0. // DPRINT4(0, "GhtDecrementRefCountByKey - ref count zero with Noop Action: %08x, %08x, %d, %s\n", pE, Hval, HvalIndex, HashTable->Name); FRS_ASSERT(!"ref count zero-6"); ReleaseListLock(&RowEntry->HashRow); return GHT_STATUS_FAILURE; } } ReleaseListLock(&RowEntry->HashRow); return GHT_STATUS_SUCCESS; } } ); RowEntry->RowLookupFails += 1; return GHT_STATUS_NOT_FOUND; } GHT_STATUS GhtSwapEntryByAddress( PGENERIC_HASH_TABLE HashTable, PVOID OldHashEntryArg, PVOID NewHashEntryArg, BOOL WaitIfLocked ) /*++ Routine Description: This routine replaces an existing old hash entry with a new entry. It verifies tha the old hash entry is still in the table. It assumes that the key value of the new entry is the same as the old entry. NO CHECK IS MADE. The expected use is when the caller needs to reallocate an entry with more storage. NOTE ALSO: The reference count is copied from the old entry to the new one. Using this routine means that the caller is using GhtDecrementRefCountByKey() and GhtIncrementRefCountByKey() to access the ref counts on any element in the table since the entry could get swapped making the pointer invalid. TBI - Return GHT_STATUS_LOCKCONFLICT if we failed to get the lock and WaitIfLocked was FALSE. Note: This function is only safe if you have a reference on the entry otherwise another thread could have already deleted the entry and your entry address is pointing at freed memory. A Lookup which gave you the address bumps the reference count. An insert in which you kept the address does NOT bump the reference count. Arguments: HashTable -- ptr to a GENERIC_HASH_TABLE struct. OldHashEntry -- ptr to entry to swap out of table. NewHashEntry -- ptr to entry to swap in to table. WaitIfLocked -- True means wait if the row is locked. Return Value: GHT_STATUS_SUCCESS if swap ok. GHT_STATUS_NOT_FOUND if old entry not in table. --*/ // Note: TBD if necc, implement GhtIncrementRefCountByKey. { #undef DEBSUB #define DEBSUB "GhtSwapEntryByAddress:" ULONG Hval, HvalIndex; PGENERIC_HASH_ROW_ENTRY RowEntry; PLIST_ENTRY BeforeEntry; BOOL Found; PGENERIC_HASH_ENTRY_HEADER Entry; PGENERIC_HASH_ENTRY_HEADER NewHashEntry = (PGENERIC_HASH_ENTRY_HEADER)NewHashEntryArg; PGENERIC_HASH_ENTRY_HEADER OldHashEntry = (PGENERIC_HASH_ENTRY_HEADER)OldHashEntryArg; // // Get the hash value for the element and compute the index and RowEntry // address. Then get the row lock. // Hval = OldHashEntry->HashValue; HvalIndex = Hval % HashTable->NumberRows; RowEntry = HashTable->HashRowBase + HvalIndex; FrsRtlAcquireListLock(&RowEntry->HashRow); // // check if the entry is actually on the List. // // Walk the list with the lock looking for a match on the // the hash value then try and match the KeyValue. // Entry = GhtQuickCheck(HashTable, RowEntry, OldHashEntry, Hval); if (Entry != OldHashEntry) { DPRINT4(0, "GhtSwapEntryByAddress - entry not on list %08x, %08x, %d, %s\n", OldHashEntry, Hval, HvalIndex, HashTable->Name); FrsRtlReleaseListLock(&RowEntry->HashRow); return GHT_STATUS_NOT_FOUND; } // // Copy the ref count and hash value from the old entry to the new one. // NewHashEntry->ReferenceCount = OldHashEntry->ReferenceCount; NewHashEntry->HashValue = OldHashEntry->HashValue; // // Pull the old entry out and replace with the new entry. // List counts do not change so do list juggling here. // BeforeEntry = OldHashEntry->ListEntry.Flink; FrsRemoveEntryList(&OldHashEntry->ListEntry); InsertTailList(BeforeEntry, &NewHashEntry->ListEntry); FrsRtlReleaseListLock(&RowEntry->HashRow); return GHT_STATUS_SUCCESS; }