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.
1439 lines
37 KiB
1439 lines
37 KiB
/*++
|
|
|
|
Copyright (c) 1991 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
HashSup.c
|
|
|
|
Abstract:
|
|
|
|
This module implements the Ntfs hasing support routines
|
|
|
|
Author:
|
|
|
|
Chris Davis [CDavis] 2-May-1997
|
|
Brian Andrew [BrianAn] 29-Dec-1998
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include "NtfsProc.h"
|
|
|
|
//
|
|
// The Bug check file id for this module
|
|
//
|
|
|
|
#define BugCheckFileId (NTFS_BUG_CHECK_HASHSUP)
|
|
|
|
//
|
|
// The debug trace level for this module
|
|
//
|
|
|
|
#define Dbg (DEBUG_TRACE_HASHSUP)
|
|
|
|
/*
|
|
Here are 10 primes slightly greater than 10^9 which may come in handy
|
|
1000000007, 1000000009, 1000000021, 1000000033, 1000000087,
|
|
1000000093, 1000000097, 1000000103, 1000000123, 1000000181
|
|
*/
|
|
|
|
//
|
|
// Local definitions
|
|
//
|
|
|
|
//
|
|
// Hash value is modula this value.
|
|
//
|
|
|
|
#define HASH_PRIME (1048583)
|
|
|
|
//
|
|
// Bucket depth before starting to split.
|
|
//
|
|
|
|
#ifdef NTFS_HASH_DATA
|
|
#define HASH_MAX_BUCKET_DEPTH (7)
|
|
ULONG NtfsInsertHashCount = 0;
|
|
BOOLEAN NtfsFillHistogram = FALSE;
|
|
VOID
|
|
NtfsFillHashHistogram (
|
|
PNTFS_HASH_TABLE Table
|
|
);
|
|
#else
|
|
#define HASH_MAX_BUCKET_DEPTH (5)
|
|
#endif
|
|
|
|
//
|
|
// VOID
|
|
// NtfsHashBucketFromHash (
|
|
// IN PNTFS_HASH_TABLE Table,
|
|
// IN ULONG Hash,
|
|
// OUT PULONG Index
|
|
// );
|
|
//
|
|
|
|
#define NtfsHashBucketFromHash(T,H,PI) { \
|
|
*(PI) = (H) & ((T)->MaxBucket - 1); \
|
|
if (*(PI) < (T)->SplitPoint) { \
|
|
*(PI) = (H) & ((2 * (T)->MaxBucket) - 1); \
|
|
} \
|
|
}
|
|
|
|
//
|
|
// VOID
|
|
// NtfsGetHashSegmentAndIndex (
|
|
// IN ULONG HashBucket,
|
|
// IN PULONG HashSegment,
|
|
// IN PULONG HashIndex
|
|
// );
|
|
//
|
|
|
|
#define NtfsGetHashSegmentAndIndex(B,S,I) { \
|
|
*(S) = (B) >> HASH_INDEX_SHIFT; \
|
|
*(I) = (B) & (HASH_MAX_INDEX_COUNT - 1);\
|
|
}
|
|
|
|
|
|
//
|
|
// Local procedures
|
|
//
|
|
|
|
VOID
|
|
NtfsInitHashSegment (
|
|
IN OUT PNTFS_HASH_TABLE Table,
|
|
IN ULONG SegmentIndex
|
|
);
|
|
|
|
PNTFS_HASH_ENTRY
|
|
NtfsLookupHashEntry (
|
|
IN PNTFS_HASH_TABLE Table,
|
|
IN ULONG FullNameLength,
|
|
IN ULONG HashValue,
|
|
IN PNTFS_HASH_ENTRY CurrentEntry OPTIONAL
|
|
);
|
|
|
|
BOOLEAN
|
|
NtfsAreHashNamesEqual (
|
|
IN PSCB StartingScb,
|
|
IN PLCB HashLcb,
|
|
IN PUNICODE_STRING RelativeName
|
|
);
|
|
|
|
VOID
|
|
NtfsExpandHashTable (
|
|
IN OUT PNTFS_HASH_TABLE Table
|
|
);
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
#pragma alloc_text(PAGE, NtfsAreHashNamesEqual)
|
|
#pragma alloc_text(PAGE, NtfsExpandHashTable)
|
|
#pragma alloc_text(PAGE, NtfsFindPrefixHashEntry)
|
|
#pragma alloc_text(PAGE, NtfsInitHashSegment)
|
|
#pragma alloc_text(PAGE, NtfsInitializeHashTable)
|
|
#pragma alloc_text(PAGE, NtfsInsertHashEntry)
|
|
#pragma alloc_text(PAGE, NtfsLookupHashEntry)
|
|
#pragma alloc_text(PAGE, NtfsRemoveHashEntry)
|
|
#pragma alloc_text(PAGE, NtfsUninitializeHashTable)
|
|
#endif
|
|
|
|
|
|
VOID
|
|
NtfsInitializeHashTable (
|
|
IN OUT PNTFS_HASH_TABLE Table
|
|
)
|
|
|
|
/*++
|
|
Routine Description:
|
|
|
|
This routine is called to initialize the hash table. We set this up with a single
|
|
hash segment. May raise due to InitHashSegment
|
|
|
|
Arguments:
|
|
|
|
Table - Hash table to initialize.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
PAGED_CODE();
|
|
|
|
RtlZeroMemory( Table, sizeof( NTFS_HASH_TABLE ));
|
|
NtfsInitHashSegment( Table, 0 );
|
|
|
|
Table->MaxBucket = HASH_MAX_INDEX_COUNT;
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsUninitializeHashTable (
|
|
IN OUT PNTFS_HASH_TABLE Table
|
|
)
|
|
|
|
/*++
|
|
Routine Description:
|
|
|
|
This routine will uninitialize the hash table. Note that all of the buckets should be
|
|
empty.
|
|
|
|
Arguments:
|
|
|
|
Table - Hash table.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
PNTFS_HASH_SEGMENT *ThisSegment;
|
|
PNTFS_HASH_SEGMENT *LastSegment;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Walk through the array of hash segments.
|
|
//
|
|
|
|
ThisSegment = &Table->HashSegments[0];
|
|
LastSegment = &Table->HashSegments[HASH_MAX_SEGMENT_COUNT - 1];
|
|
|
|
while (*ThisSegment != NULL) {
|
|
|
|
NtfsFreePool( *ThisSegment );
|
|
*ThisSegment = NULL;
|
|
|
|
if (ThisSegment == LastSegment) { break; }
|
|
|
|
ThisSegment += 1;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
PLCB
|
|
NtfsFindPrefixHashEntry (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PNTFS_HASH_TABLE Table,
|
|
IN PSCB ParentScb,
|
|
OUT PULONG CreateFlags,
|
|
IN OUT PFCB *CurrentFcb,
|
|
OUT PULONG FileHashValue,
|
|
OUT PULONG FileNameLength,
|
|
OUT PULONG ParentHashValue,
|
|
OUT PULONG ParentNameLength,
|
|
IN OUT PUNICODE_STRING RemainingName
|
|
)
|
|
|
|
/*++
|
|
Routine Description:
|
|
|
|
This routine is called to look for a match in the hash table for the given
|
|
starting Scb and remaining name. We will first look for the full name, if
|
|
we don't find a match on that we will check for a matching parent string.
|
|
|
|
Arguments:
|
|
|
|
Table - Hash table to process.
|
|
|
|
ParentScb - The name search begins from this directory. The Scb is
|
|
initially acquired and its Fcb is stored in *CurrentFcb.
|
|
|
|
OwnParentScb - Boolean which indicates if this thread owns the parent Scb.
|
|
|
|
CurrentFcb - Points to the last Fcb acquired. If we need to perform a
|
|
teardown it will begin from this point.
|
|
|
|
FileHashValue - Address to store the hash value for the input string. Applies to
|
|
the full string and starting Scb even if a match wasn't found.
|
|
|
|
FileNameLength - Location to store the length of the relative name which
|
|
matches the hash value generated above. If we didn't generate a hash
|
|
then we will return a 0 for the length.
|
|
|
|
ParentHashValue - Address to store the hash value for the parent of the input string.
|
|
Applies to parent of the full string and starting Scb even if a match wasn't found
|
|
for the full string.
|
|
|
|
ParentNameLength - Location to store the length of the parent of the full name.
|
|
It corresponds to the parent hash generated above. Note that our caller
|
|
will have to check the remaining name on return to know if the parent hash
|
|
was computed. If we didn't generate a hash for the parent above then
|
|
we will return 0 for the length.
|
|
|
|
RemainingName - Name relative to the StartingScb above, on return it will be
|
|
the unmatched portion of the name.
|
|
|
|
Return Value:
|
|
|
|
PLCB - Pointer to the Lcb found in the hash lookup or FALSE if no Lcb is found.
|
|
If an Lcb is found then we will own the Fcb for it exclusively on return.
|
|
|
|
--*/
|
|
|
|
{
|
|
PVCB Vcb = ParentScb->Vcb;
|
|
PNTFS_HASH_ENTRY FoundEntry;
|
|
PLCB FoundLcb = NULL;
|
|
UNICODE_STRING TempName;
|
|
WCHAR Separator = L'\\';
|
|
ULONG RemainingNameLength;
|
|
PWCHAR RemainingNameBuffer;
|
|
PWCHAR NextChar;
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT( RemainingName->Length != 0 );
|
|
ASSERT( RemainingName->Buffer[0] != L'\\' );
|
|
ASSERT( RemainingName->Buffer[0] != L':' );
|
|
ASSERT( ParentScb->AttributeTypeCode == $INDEX_ALLOCATION );
|
|
|
|
//
|
|
// Compute the hash for the file before acquiring the hash table.
|
|
//
|
|
|
|
*ParentHashValue = *FileHashValue = ParentScb->ScbType.Index.HashValue;
|
|
|
|
//
|
|
// Check whether we need to generate the separator.
|
|
//
|
|
|
|
if (ParentScb != Vcb->RootIndexScb) {
|
|
|
|
NtfsConvertNameToHash( &Separator, sizeof( WCHAR ), Vcb->UpcaseTable, FileHashValue );
|
|
}
|
|
|
|
//
|
|
// Generate the hash for the file name.
|
|
//
|
|
|
|
NtfsConvertNameToHash( RemainingName->Buffer,
|
|
RemainingName->Length,
|
|
Vcb->UpcaseTable,
|
|
FileHashValue );
|
|
|
|
*FileHashValue = NtfsGenerateHashFromUlong( *FileHashValue );
|
|
|
|
|
|
NtfsAcquireHashTable( Vcb );
|
|
|
|
//
|
|
// Generate the hash value based on the starting Scb and the string.
|
|
// Return immediately if there is no parent name.
|
|
//
|
|
|
|
if (ParentScb->ScbType.Index.NormalizedName.Length == 0) {
|
|
|
|
NtfsReleaseHashTable( Vcb );
|
|
return NULL;
|
|
}
|
|
|
|
*ParentNameLength = *FileNameLength = ParentScb->ScbType.Index.NormalizedName.Length;
|
|
*FileNameLength += RemainingName->Length;
|
|
|
|
#ifdef NTFS_HASH_DATA
|
|
Table->HashLookupCount += 1;
|
|
#endif
|
|
|
|
//
|
|
// Check whether to include a separator.
|
|
//
|
|
|
|
if (ParentScb != Vcb->RootIndexScb) {
|
|
|
|
*FileNameLength += sizeof( WCHAR );
|
|
}
|
|
|
|
//
|
|
// Loop looking for a match on the hash value and then verify the name.
|
|
//
|
|
|
|
FoundEntry = NULL;
|
|
|
|
while ((FoundEntry = NtfsLookupHashEntry( Table,
|
|
*FileNameLength,
|
|
*FileHashValue,
|
|
FoundEntry )) != NULL) {
|
|
|
|
//
|
|
// If we have a match then verify the name strings.
|
|
//
|
|
|
|
if (NtfsAreHashNamesEqual( ParentScb,
|
|
FoundEntry->HashLcb,
|
|
RemainingName )) {
|
|
|
|
//
|
|
// The name string match. Adjust the input remaining name to
|
|
// show there is no name left to process.
|
|
//
|
|
|
|
FoundLcb = FoundEntry->HashLcb;
|
|
|
|
//
|
|
// Move to the end of the input string.
|
|
//
|
|
|
|
RemainingNameLength = 0;
|
|
RemainingNameBuffer = Add2Ptr( RemainingName->Buffer,
|
|
RemainingName->Length );
|
|
|
|
//
|
|
// Show that we never generated a parent hash. No need to
|
|
// remember the file hash in this case either.
|
|
//
|
|
|
|
#ifdef NTFS_HASH_DATA
|
|
Table->FileMatchCount += 1;
|
|
#endif
|
|
*ParentNameLength = 0;
|
|
*FileNameLength = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we don't have a match then let's look at a possible parent string.
|
|
//
|
|
|
|
if (FoundLcb == NULL) {
|
|
|
|
//
|
|
// Search backwards for a '\'. If it is a '\' then do the
|
|
// same search for a match on the string based on the parent.
|
|
//
|
|
|
|
TempName.Length = RemainingName->Length;
|
|
NextChar = &RemainingName->Buffer[ (TempName.Length - sizeof( WCHAR )) / sizeof( WCHAR ) ];
|
|
|
|
while (TRUE) {
|
|
|
|
//
|
|
// Break out if no separator is found.
|
|
//
|
|
|
|
if (TempName.Length == 0) {
|
|
|
|
*ParentNameLength = 0;
|
|
break;
|
|
}
|
|
|
|
if (*NextChar == L'\\') {
|
|
|
|
//
|
|
// We found the separator. Back up one more character to step over
|
|
// the '\' character and then complete a hash for the parent.
|
|
//
|
|
|
|
TempName.Buffer = RemainingName->Buffer;
|
|
TempName.Length -= sizeof( WCHAR );
|
|
TempName.MaximumLength = TempName.Length;
|
|
|
|
//
|
|
// Drop the mutex while we compute the hash.
|
|
//
|
|
|
|
NtfsReleaseHashTable( Vcb );
|
|
|
|
if (ParentScb != Vcb->RootIndexScb) {
|
|
|
|
NtfsConvertNameToHash( &Separator, sizeof( WCHAR ), Vcb->UpcaseTable, ParentHashValue );
|
|
*ParentNameLength += sizeof( WCHAR );
|
|
}
|
|
|
|
NtfsConvertNameToHash( TempName.Buffer,
|
|
TempName.Length,
|
|
Vcb->UpcaseTable,
|
|
ParentHashValue );
|
|
|
|
*ParentHashValue = NtfsGenerateHashFromUlong( *ParentHashValue );
|
|
*ParentNameLength += TempName.Length;
|
|
|
|
NtfsAcquireHashTable( Vcb );
|
|
|
|
FoundEntry = NULL;
|
|
while ((FoundEntry = NtfsLookupHashEntry( Table,
|
|
*ParentNameLength,
|
|
*ParentHashValue,
|
|
FoundEntry )) != NULL) {
|
|
|
|
//
|
|
// If we have a match then verify the name strings.
|
|
//
|
|
|
|
if (NtfsAreHashNamesEqual( ParentScb,
|
|
FoundEntry->HashLcb,
|
|
&TempName )) {
|
|
|
|
//
|
|
// The name string match. Adjust the remaining name to
|
|
// swallow the parent string found.
|
|
//
|
|
|
|
FoundLcb = FoundEntry->HashLcb;
|
|
|
|
RemainingNameLength = RemainingName->Length - (TempName.Length + sizeof( WCHAR ));
|
|
RemainingNameBuffer = Add2Ptr( RemainingName->Buffer,
|
|
TempName.Length + sizeof( WCHAR ));
|
|
|
|
#ifdef NTFS_HASH_DATA
|
|
Table->ParentMatchCount += 1;
|
|
#endif
|
|
*ParentNameLength = 0;
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// No match found. Break out in any case.
|
|
//
|
|
|
|
break;
|
|
}
|
|
|
|
TempName.Length -= sizeof( WCHAR );
|
|
NextChar -= 1;
|
|
}
|
|
}
|
|
|
|
//
|
|
// We now have the Lcb to return. We need to carefully acquire the Fcb for this Lcb.
|
|
// We can't acquire it while waiting because of deadlock possibilities.
|
|
//
|
|
|
|
if (FoundLcb != NULL) {
|
|
|
|
UCHAR LcbFlags;
|
|
BOOLEAN CreateNewLcb = FALSE;
|
|
ULONG RemainingNameOffset;
|
|
|
|
//
|
|
// While we own the hash table it will be safe to copy the exact case of the
|
|
// names over to our input buffer. We will work our way backwards through the
|
|
// remaining name passed to us.
|
|
//
|
|
|
|
RemainingNameOffset = RemainingNameLength + FoundLcb->ExactCaseLink.LinkName.Length;
|
|
|
|
//
|
|
// If this was a match on the parent then step back over the '\'.
|
|
// We know there must be a separator.
|
|
//
|
|
|
|
if (RemainingNameLength != 0) {
|
|
|
|
RemainingNameOffset += sizeof( WCHAR );
|
|
}
|
|
|
|
//
|
|
// Now back up the length of the name in the Lcb. Save this location in
|
|
// case we have to look up the Lcb again.
|
|
//
|
|
|
|
TempName.Buffer = Add2Ptr( RemainingName->Buffer,
|
|
RemainingName->Length - RemainingNameOffset );
|
|
|
|
TempName.MaximumLength = TempName.Length = FoundLcb->ExactCaseLink.LinkName.Length;
|
|
|
|
RtlCopyMemory( TempName.Buffer,
|
|
FoundLcb->ExactCaseLink.LinkName.Buffer,
|
|
FoundLcb->ExactCaseLink.LinkName.Length );
|
|
|
|
//
|
|
// Now the balance of the name which is part of the Lcb->Scb parent name.
|
|
//
|
|
|
|
if (RemainingNameOffset != RemainingName->Length) {
|
|
|
|
//
|
|
// There are prior components in our input string. We want to back up
|
|
// over the preceding backslash and then copy over the relevant portion
|
|
// of the normalized name.
|
|
//
|
|
|
|
RemainingNameOffset = RemainingName->Length - (RemainingNameOffset + sizeof( WCHAR ));
|
|
|
|
RtlCopyMemory( RemainingName->Buffer,
|
|
Add2Ptr( FoundLcb->Scb->ScbType.Index.NormalizedName.Buffer,
|
|
FoundLcb->Scb->ScbType.Index.NormalizedName.Length - RemainingNameOffset ),
|
|
RemainingNameOffset );
|
|
}
|
|
|
|
if (!NtfsAcquireFcbWithPaging( IrpContext, FoundLcb->Fcb, ACQUIRE_DONT_WAIT )) {
|
|
|
|
PFCB ThisFcb = FoundLcb->Fcb;
|
|
PFCB ParentFcb = FoundLcb->Scb->Fcb;
|
|
PSCB ThisScb;
|
|
|
|
//
|
|
// Remember the current Lcb flags.
|
|
//
|
|
|
|
LcbFlags = FoundLcb->FileNameAttr->Flags;
|
|
|
|
//
|
|
// Acquire the Fcb table and reference the Fcb. Then release the hash table, Fcb table
|
|
// and ParentScb. We should now be able to acquire the Fcb. Reacquire the Fcb table
|
|
// to clean up the Fcb reference count. Finally verify that the Lcb is still in the
|
|
// hash table (requires another lookup).
|
|
//
|
|
|
|
NtfsAcquireFcbTable( IrpContext, Vcb );
|
|
ThisFcb->ReferenceCount += 1;
|
|
ParentFcb->ReferenceCount += 1;
|
|
NtfsReleaseFcbTable( IrpContext, Vcb );
|
|
|
|
NtfsReleaseScb( IrpContext, ParentScb );
|
|
ClearFlag( *CreateFlags, CREATE_FLAG_SHARED_PARENT_FCB );
|
|
*CurrentFcb = NULL;
|
|
|
|
NtfsReleaseHashTable( Vcb );
|
|
|
|
NtfsAcquireFcbWithPaging( IrpContext, ThisFcb, 0 );
|
|
*CurrentFcb = ThisFcb;
|
|
NtfsAcquireSharedFcb( IrpContext, ParentFcb, NULL, 0 );
|
|
|
|
NtfsAcquireFcbTable( IrpContext, Vcb );
|
|
ThisFcb->ReferenceCount -= 1;
|
|
ParentFcb->ReferenceCount -= 1;
|
|
NtfsReleaseFcbTable( IrpContext, Vcb );
|
|
|
|
//
|
|
// Now look for an existing Scb and Lcb.
|
|
//
|
|
|
|
ThisScb = NtfsCreateScb( IrpContext,
|
|
ParentFcb,
|
|
$INDEX_ALLOCATION,
|
|
&NtfsFileNameIndex,
|
|
TRUE,
|
|
NULL );
|
|
|
|
if (ThisScb == NULL) {
|
|
|
|
#ifdef NTFS_HASH_DATA
|
|
Table->CreateScbFails += 1;
|
|
#endif
|
|
NtfsReleaseFcb( IrpContext, ParentFcb );
|
|
NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
|
|
}
|
|
|
|
FoundLcb = NtfsCreateLcb( IrpContext,
|
|
ThisScb,
|
|
ThisFcb,
|
|
TempName,
|
|
LcbFlags,
|
|
&CreateNewLcb );
|
|
|
|
NtfsReleaseFcb( IrpContext, ParentFcb );
|
|
|
|
//
|
|
// If this wasn't an existing Lcb then reacquire the starting Scb.
|
|
// This is the rare case so raise CANT_WAIT and retry.
|
|
//
|
|
|
|
if (FoundLcb == NULL) {
|
|
|
|
#ifdef NTFS_HASH_DATA
|
|
Table->CreateLcbFails += 1;
|
|
#endif
|
|
NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
|
|
}
|
|
|
|
//
|
|
// Release the starting Scb and remember our current Fcb.
|
|
//
|
|
|
|
} else {
|
|
|
|
NtfsReleaseHashTable( Vcb );
|
|
|
|
NtfsReleaseScb( IrpContext, ParentScb );
|
|
ClearFlag( *CreateFlags, CREATE_FLAG_SHARED_PARENT_FCB );
|
|
*CurrentFcb = FoundLcb->Fcb;
|
|
}
|
|
|
|
//
|
|
// If we still have the Lcb then update the remaining name string.
|
|
//
|
|
|
|
if (FoundLcb != NULL) {
|
|
|
|
RemainingName->Length = (USHORT) RemainingNameLength;
|
|
RemainingName->Buffer = RemainingNameBuffer;
|
|
}
|
|
|
|
} else {
|
|
|
|
NtfsReleaseHashTable( Vcb );
|
|
}
|
|
|
|
return FoundLcb;
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsInsertHashEntry (
|
|
IN PNTFS_HASH_TABLE Table,
|
|
IN PLCB HashLcb,
|
|
IN ULONG NameLength,
|
|
IN ULONG HashValue
|
|
)
|
|
|
|
/*++
|
|
Routine Description:
|
|
|
|
This routine will insert a entry into the hash table. May raise due to
|
|
memory allocation.
|
|
|
|
Arguments:
|
|
|
|
Table - Hash table.
|
|
|
|
HashLcb - Final target of the hash operation.
|
|
|
|
NameLength - Full path used to reach the hash value.
|
|
|
|
HashValue - Hash value to insert.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
PNTFS_HASH_ENTRY NewHashEntry;
|
|
|
|
ULONG Segment;
|
|
ULONG Index;
|
|
|
|
ULONG Bucket;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Allocate and initialize the hash entry. Nothing to do if unsuccessful.
|
|
//
|
|
|
|
NewHashEntry = NtfsAllocatePoolNoRaise( PagedPool, sizeof( NTFS_HASH_ENTRY ));
|
|
|
|
if (NewHashEntry == NULL) {
|
|
|
|
return;
|
|
}
|
|
|
|
NewHashEntry->HashValue = HashValue;
|
|
NewHashEntry->FullNameLength = NameLength;
|
|
NewHashEntry->HashLcb = HashLcb;
|
|
|
|
//
|
|
// Find the bucket to insert into and then do the insertion.
|
|
//
|
|
|
|
NtfsAcquireHashTable( HashLcb->Fcb->Vcb );
|
|
|
|
//
|
|
// Continue the process of growing the table if needed.
|
|
//
|
|
|
|
if (Table->TableState == TABLE_STATE_EXPANDING) {
|
|
|
|
NtfsExpandHashTable( Table );
|
|
}
|
|
|
|
NtfsHashBucketFromHash( Table, HashValue, &Bucket );
|
|
NtfsGetHashSegmentAndIndex( Bucket, &Segment, &Index );
|
|
|
|
NewHashEntry->NextEntry = (*Table->HashSegments[ Segment ])[ Index ];
|
|
(*Table->HashSegments[ Segment ])[ Index ] = NewHashEntry;
|
|
|
|
#ifdef NTFS_HASH_DATA
|
|
NtfsInsertHashCount += 1;
|
|
|
|
if (!FlagOn( NtfsInsertHashCount, 0xff ) && NtfsFillHistogram) {
|
|
|
|
NtfsFillHashHistogram( Table );
|
|
}
|
|
#endif
|
|
|
|
NtfsReleaseHashTable( HashLcb->Fcb->Vcb );
|
|
|
|
//
|
|
// Remember that we've inserted a hash.
|
|
//
|
|
|
|
HashLcb->HashValue = HashValue;
|
|
SetFlag( HashLcb->LcbState, LCB_STATE_VALID_HASH_VALUE );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsRemoveHashEntry (
|
|
IN PNTFS_HASH_TABLE Table,
|
|
IN PLCB HashLcb
|
|
)
|
|
|
|
/*++
|
|
Routine Description:
|
|
|
|
This routine will remove all entries with a given hash value for the given Lcb.
|
|
|
|
Arguments:
|
|
|
|
Table - Hash table.
|
|
|
|
HashLcb - Final target of the hash operation.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
PNTFS_HASH_ENTRY *NextHashEntry;
|
|
PNTFS_HASH_ENTRY CurrentEntry;
|
|
|
|
ULONG Segment;
|
|
ULONG Index;
|
|
|
|
ULONG Bucket;
|
|
|
|
ULONG BucketDepth = 0;
|
|
|
|
PAGED_CODE();
|
|
|
|
NtfsAcquireHashTable( HashLcb->Fcb->Vcb );
|
|
|
|
//
|
|
// Find the bucket to remove from and then search for this hash value.
|
|
//
|
|
|
|
NtfsHashBucketFromHash( Table, HashLcb->HashValue, &Bucket );
|
|
NtfsGetHashSegmentAndIndex( Bucket, &Segment, &Index );
|
|
|
|
//
|
|
// Get the address of the first entry.
|
|
//
|
|
|
|
NextHashEntry = (PNTFS_HASH_ENTRY *) &(*Table->HashSegments[ Segment ])[ Index ];
|
|
|
|
while (*NextHashEntry != NULL) {
|
|
|
|
//
|
|
// Look for a match entry.
|
|
//
|
|
|
|
if (((*NextHashEntry)->HashValue == HashLcb->HashValue) &&
|
|
((*NextHashEntry)->HashLcb == HashLcb)) {
|
|
|
|
CurrentEntry = *NextHashEntry;
|
|
|
|
*NextHashEntry = CurrentEntry->NextEntry;
|
|
|
|
NtfsFreePool( CurrentEntry );
|
|
|
|
//
|
|
// Move to the next entry but remember the depth of the bucket.
|
|
//
|
|
|
|
} else {
|
|
|
|
NextHashEntry = &(*NextHashEntry)->NextEntry;
|
|
BucketDepth += 1;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Check if the bucket depth is greater than our max.
|
|
//
|
|
|
|
if ((BucketDepth > HASH_MAX_BUCKET_DEPTH) &&
|
|
(Table->TableState == TABLE_STATE_STABLE) &&
|
|
(Table->MaxBucket < HASH_MAX_BUCKET_COUNT)) {
|
|
|
|
ASSERT( Table->SplitPoint == 0 );
|
|
Table->TableState = TABLE_STATE_EXPANDING;
|
|
}
|
|
|
|
NtfsReleaseHashTable( HashLcb->Fcb->Vcb );
|
|
|
|
HashLcb->HashValue = 0;
|
|
ClearFlag( HashLcb->LcbState, LCB_STATE_VALID_HASH_VALUE );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
VOID
|
|
NtfsInitHashSegment (
|
|
IN OUT PNTFS_HASH_TABLE Table,
|
|
IN ULONG SegmentIndex
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine allocates and initializes a new segment in the segment array.
|
|
It may raise out of resources.
|
|
|
|
Arguments:
|
|
|
|
Table - Table with an entry to initialize.
|
|
|
|
SegmentIndex - Index to be initialized.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
PAGED_CODE();
|
|
|
|
Table->HashSegments[ SegmentIndex ] = NtfsAllocatePool( PagedPool, sizeof( NTFS_HASH_SEGMENT ));
|
|
RtlZeroMemory( Table->HashSegments[ SegmentIndex ], sizeof( NTFS_HASH_SEGMENT ));
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
PNTFS_HASH_ENTRY
|
|
NtfsLookupHashEntry (
|
|
IN PNTFS_HASH_TABLE Table,
|
|
IN ULONG FullNameLength,
|
|
IN ULONG HashValue,
|
|
IN PNTFS_HASH_ENTRY CurrentEntry OPTIONAL
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine looks up a match in the hash table for a given hash value.
|
|
The entry is uniquely indentified by the hash value, and the full name length.
|
|
This routine also takes a pointer to a hash entry for the case where we are
|
|
resuming a search for the same hash value.
|
|
|
|
If the target bucket has more than our optimal number of entries then set
|
|
the state of the table to grow the number of buckets.
|
|
|
|
Arguments:
|
|
|
|
Table - Hash table to search
|
|
|
|
FullNameLength - Number of bytes in the name relative to the root.
|
|
|
|
HashValue - Precomputed hash value.
|
|
|
|
CurrentEntry - NULL if this is the first search for this hash entry. Otherwise
|
|
it is the last entry returned.
|
|
|
|
Return Value:
|
|
|
|
PNTFS_HASH_ENTRY - this is NULL if no match was found. Otherwise it points it to
|
|
a hash entry which matches the input value. NOTE - the caller must then verify
|
|
the name strings.
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG ChainDepth = 0;
|
|
PNTFS_HASH_ENTRY NextEntry;
|
|
ULONG HashBucket;
|
|
ULONG HashSegment;
|
|
ULONG HashIndex;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// If we weren't passed an initial hash entry then look up the start of
|
|
// the chain for the bucket containing this hash value.
|
|
//
|
|
|
|
if (!ARGUMENT_PRESENT( CurrentEntry )) {
|
|
|
|
//
|
|
// Find the bucket by computing the segment and index to look in.
|
|
//
|
|
|
|
NtfsHashBucketFromHash( Table, HashValue, &HashBucket );
|
|
NtfsGetHashSegmentAndIndex( HashBucket, &HashSegment, &HashIndex );
|
|
|
|
//
|
|
// Get the first entry in the bucket.
|
|
//
|
|
|
|
NextEntry = (*Table->HashSegments[ HashSegment ])[ HashIndex ];
|
|
|
|
//
|
|
// Otherwise we use the next entry in the chain.
|
|
//
|
|
|
|
} else {
|
|
|
|
NextEntry = CurrentEntry->NextEntry;
|
|
}
|
|
|
|
//
|
|
// Walk down the chain looking for a match. Keep track of the depth
|
|
// of the chain in case we need to grow the table.
|
|
//
|
|
|
|
while (NextEntry != NULL) {
|
|
|
|
ChainDepth += 1;
|
|
|
|
if ((NextEntry->HashValue == HashValue) &&
|
|
(NextEntry->FullNameLength == FullNameLength)) {
|
|
|
|
break;
|
|
}
|
|
|
|
NextEntry = NextEntry->NextEntry;
|
|
}
|
|
|
|
//
|
|
// If the depth is greater than our optimal value then mark the table
|
|
// for expansion. The table may already be growing or at its maximum
|
|
// value.
|
|
//
|
|
|
|
if ((ChainDepth > HASH_MAX_BUCKET_DEPTH) &&
|
|
(Table->TableState == TABLE_STATE_STABLE) &&
|
|
(Table->MaxBucket < HASH_MAX_BUCKET_COUNT)) {
|
|
|
|
ASSERT( Table->SplitPoint == 0 );
|
|
Table->TableState = TABLE_STATE_EXPANDING;
|
|
}
|
|
|
|
//
|
|
// Return the value if found.
|
|
//
|
|
|
|
return NextEntry;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
BOOLEAN
|
|
NtfsAreHashNamesEqual (
|
|
IN PSCB StartingScb,
|
|
IN PLCB HashLcb,
|
|
IN PUNICODE_STRING RelativeName
|
|
)
|
|
|
|
/*++
|
|
Routine Description:
|
|
|
|
This routine is called to verify that the match found in the hash table has the
|
|
same name as the input string.
|
|
|
|
Arguments:
|
|
|
|
StartingScb - The name search begins from this directory. It is not
|
|
necessarily the parent of the file being opened.
|
|
|
|
HashLcb - This is the Lcb found in the hash table. This Lcb points
|
|
directly to the full string matched.
|
|
|
|
StartingName - This is the name we need to match. It is 1 OR MORE of the
|
|
final components of the name.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
PUNICODE_STRING StartingScbName;
|
|
PUNICODE_STRING HashScbName;
|
|
UNICODE_STRING RemainingHashScbName;
|
|
UNICODE_STRING RemainingRelativeName;
|
|
USHORT SeparatorBias = 0;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Start by verifying that there is a '\' separator in the correct positions.
|
|
// There must be a separator in the Relative name prior to the last component.
|
|
// There should also be a separator in the normalized name of the Scb in the
|
|
// HashLcb where the StartingScb ends.
|
|
//
|
|
|
|
//
|
|
// If the HashLcb Scb is not the StartingScb then there must be a separator
|
|
// where the StartingScb string ends.
|
|
//
|
|
|
|
StartingScbName = &StartingScb->ScbType.Index.NormalizedName;
|
|
HashScbName = &HashLcb->Scb->ScbType.Index.NormalizedName;
|
|
|
|
//
|
|
// If there is no normalized name in this Scb then get out.
|
|
//
|
|
|
|
if (HashScbName->Length == 0) {
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
if (StartingScb != HashLcb->Scb) {
|
|
|
|
//
|
|
// Also get out if name in the StartingScb is longer than the one in the
|
|
// HashScb. Obviously there is no match if the last component of the
|
|
// HashScb is longer than the last one or more components in
|
|
// the input name. We can use >= as the test because if the lengths
|
|
// match but they aren't the same Scb then there can be no match either.
|
|
//
|
|
|
|
if (StartingScbName->Length >= HashScbName->Length) {
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Check for the separator provided the starting Scb is not the root.
|
|
//
|
|
|
|
if (StartingScb != StartingScb->Vcb->RootIndexScb) {
|
|
|
|
if (HashScbName->Buffer[ StartingScbName->Length / sizeof( WCHAR ) ] != L'\\') {
|
|
|
|
return FALSE;
|
|
|
|
//
|
|
// Make sure the StartingScbName and the first part of the HashScbName
|
|
// match. If not, this is definitely not the right hash entry.
|
|
//
|
|
|
|
} else {
|
|
|
|
RemainingHashScbName.Buffer = HashScbName->Buffer;
|
|
RemainingHashScbName.MaximumLength =
|
|
RemainingHashScbName.Length = StartingScbName->Length;
|
|
|
|
//
|
|
// OK to do a direct memory compare here because both name fragments
|
|
// are in the normalized form (exactly as on disk).
|
|
//
|
|
|
|
if (!NtfsAreNamesEqual( StartingScb->Vcb->UpcaseTable,
|
|
StartingScbName,
|
|
&RemainingHashScbName,
|
|
FALSE )) {
|
|
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
SeparatorBias = sizeof( WCHAR );
|
|
}
|
|
|
|
//
|
|
// Set up a unicode string for the remaining portion of the hash scb name.
|
|
//
|
|
|
|
RemainingHashScbName.Buffer = Add2Ptr( HashScbName->Buffer,
|
|
StartingScbName->Length + SeparatorBias );
|
|
|
|
RemainingHashScbName.MaximumLength =
|
|
RemainingHashScbName.Length = HashScbName->Length - (StartingScbName->Length + SeparatorBias);
|
|
}
|
|
|
|
RemainingRelativeName.MaximumLength =
|
|
RemainingRelativeName.Length = HashLcb->IgnoreCaseLink.LinkName.Length;
|
|
RemainingRelativeName.Buffer = Add2Ptr( RelativeName->Buffer,
|
|
RelativeName->Length - RemainingRelativeName.Length );
|
|
|
|
//
|
|
// Check for a separator between the last component of relative name and its parent.
|
|
// Verify the parent portion actually exists.
|
|
//
|
|
|
|
if (RemainingRelativeName.Length != RelativeName->Length) {
|
|
|
|
if (*(RemainingRelativeName.Buffer - 1) != L'\\') {
|
|
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now verify that the tail of the name matches the name in the Lcb.
|
|
//
|
|
// OK to do a direct memory compare here because both name fragments
|
|
// are already upcased.
|
|
//
|
|
//
|
|
|
|
if (!NtfsAreNamesEqual( StartingScb->Vcb->UpcaseTable,
|
|
&HashLcb->IgnoreCaseLink.LinkName,
|
|
&RemainingRelativeName,
|
|
FALSE )) {
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// It is possible that the StartingScb matches the Scb in the HashLcb. If it doesn't
|
|
// then verify the other names in the name string.
|
|
//
|
|
|
|
if (StartingScb != HashLcb->Scb) {
|
|
|
|
RemainingRelativeName.MaximumLength =
|
|
RemainingRelativeName.Length = RemainingHashScbName.Length;
|
|
RemainingRelativeName.Buffer = RelativeName->Buffer;
|
|
|
|
//
|
|
// We must to a case-insensitive compare here because the
|
|
// HashScbName is in normalized form but the RemainingRelativeName
|
|
// is already upcased.
|
|
//
|
|
|
|
if (!NtfsAreNamesEqual( StartingScb->Vcb->UpcaseTable,
|
|
&RemainingHashScbName,
|
|
&RemainingRelativeName,
|
|
TRUE )) {
|
|
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routines
|
|
//
|
|
|
|
|
|
VOID
|
|
NtfsExpandHashTable(
|
|
IN OUT PNTFS_HASH_TABLE Table
|
|
)
|
|
|
|
/*++
|
|
Routine Description:
|
|
|
|
This routine is called to add a single bucket to the hash table. If we are at the
|
|
last bucket then set the hash table state to stable.
|
|
|
|
Arguments:
|
|
|
|
Table - Hash table to add a bucket to.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
PNTFS_HASH_ENTRY *CurrentOldEntry;
|
|
PNTFS_HASH_ENTRY *CurrentNewEntry;
|
|
PNTFS_HASH_ENTRY CurrentEntry;
|
|
|
|
ULONG OldSegment;
|
|
ULONG OldIndex;
|
|
|
|
ULONG NewSegment;
|
|
ULONG NewIndex;
|
|
|
|
ULONG NextBucket;
|
|
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Are we already at the maximum then return.
|
|
//
|
|
|
|
if (Table->MaxBucket == HASH_MAX_BUCKET_COUNT) {
|
|
|
|
Table->TableState = TABLE_STATE_STABLE;
|
|
return;
|
|
}
|
|
|
|
//
|
|
// If we have completed the split then set the state to stable and quit.
|
|
//
|
|
|
|
if (Table->MaxBucket == Table->SplitPoint) {
|
|
|
|
Table->TableState = TABLE_STATE_STABLE;
|
|
Table->MaxBucket *= 2;
|
|
Table->SplitPoint = 0;
|
|
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Check if we need allocate a new segment.
|
|
//
|
|
|
|
if (!FlagOn( Table->SplitPoint, (HASH_MAX_INDEX_COUNT - 1))) {
|
|
|
|
//
|
|
// If we can't allocate a new hash segment leave the table in its
|
|
// old state and return - we can still use it as is
|
|
//
|
|
|
|
try {
|
|
NtfsInitHashSegment( Table, (Table->MaxBucket + Table->SplitPoint) >> HASH_INDEX_SHIFT );
|
|
} except( (GetExceptionCode() == STATUS_INSUFFICIENT_RESOURCES) ?
|
|
EXCEPTION_EXECUTE_HANDLER :
|
|
EXCEPTION_CONTINUE_SEARCH ) {
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now perform the split on the next bucket.
|
|
//
|
|
|
|
NtfsGetHashSegmentAndIndex( Table->SplitPoint, &OldSegment, &OldIndex );
|
|
NtfsGetHashSegmentAndIndex( Table->MaxBucket + Table->SplitPoint, &NewSegment, &NewIndex );
|
|
CurrentOldEntry = (PNTFS_HASH_ENTRY *) &(*Table->HashSegments[ OldSegment ])[ OldIndex ];
|
|
CurrentNewEntry = (PNTFS_HASH_ENTRY *) &(*Table->HashSegments[ NewSegment ])[ NewIndex ];
|
|
|
|
Table->SplitPoint += 1;
|
|
|
|
while (*CurrentOldEntry != NULL) {
|
|
|
|
NtfsHashBucketFromHash( Table, (*CurrentOldEntry)->HashValue, &NextBucket );
|
|
|
|
//
|
|
// The entry belongs in the new bucket. Take it out of the existing
|
|
// bucket and insert it at the head of the new bucket.
|
|
//
|
|
|
|
if (NextBucket >= Table->MaxBucket) {
|
|
|
|
ASSERT( NextBucket == (Table->MaxBucket + Table->SplitPoint - 1) );
|
|
|
|
CurrentEntry = *CurrentOldEntry;
|
|
*CurrentOldEntry = CurrentEntry->NextEntry;
|
|
|
|
CurrentEntry->NextEntry = *CurrentNewEntry;
|
|
*CurrentNewEntry = CurrentEntry;
|
|
|
|
//
|
|
// Move to the next entry in the existing bucket.
|
|
//
|
|
|
|
} else {
|
|
|
|
CurrentOldEntry = &(*CurrentOldEntry)->NextEntry;
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
#ifdef NTFS_HASH_DATA
|
|
VOID
|
|
NtfsFillHashHistogram (
|
|
PNTFS_HASH_TABLE Table
|
|
)
|
|
|
|
{
|
|
ULONG CurrentBucket = 0;
|
|
ULONG Segment;
|
|
ULONG Index;
|
|
|
|
PNTFS_HASH_ENTRY NextEntry;
|
|
ULONG Count;
|
|
|
|
//
|
|
// Zero the current histogram.
|
|
//
|
|
|
|
RtlZeroMemory( Table->Histogram, sizeof( Table->Histogram ));
|
|
RtlZeroMemory( Table->ExtendedHistogram, sizeof( Table->ExtendedHistogram ));
|
|
|
|
//
|
|
// Walk through all of the buckets in use.
|
|
//
|
|
|
|
while (CurrentBucket < Table->MaxBucket + Table->SplitPoint) {
|
|
|
|
Count = 0;
|
|
|
|
NtfsGetHashSegmentAndIndex( CurrentBucket, &Segment, &Index );
|
|
|
|
NextEntry = (*Table->HashSegments[ Segment ])[ Index ];
|
|
|
|
//
|
|
// Count the number of entries in each bucket.
|
|
//
|
|
|
|
while (NextEntry != NULL) {
|
|
|
|
Count += 1;
|
|
NextEntry = NextEntry->NextEntry;
|
|
}
|
|
|
|
//
|
|
// Store it into the first histogram set if count is less than 16.
|
|
//
|
|
|
|
if (Count < 16) {
|
|
|
|
Table->Histogram[Count] += 1;
|
|
|
|
//
|
|
// Store it in the last bucket if larger than our max.
|
|
//
|
|
|
|
} else if (Count >= 32) {
|
|
|
|
Table->ExtendedHistogram[15] += 1;
|
|
|
|
//
|
|
// Otherwise store it into the extended histogram.
|
|
//
|
|
|
|
} else {
|
|
|
|
Table->ExtendedHistogram[(Count - 16) / 2] += 1;
|
|
}
|
|
|
|
CurrentBucket += 1;
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
|