|
|
/*++
Copyright (c) 1996 Microsoft Corporation
Module Name:
namcache.c
Abstract:
The following functions are provided to support name cache management in mini-rdrs. See namcache.h for a more complete description of how a mini-rdr could use name caches to help eliminate trips to the server.
Author:
David Orbits [davidor] 9-Sep-1996
Revision History:
--*/
#include "precomp.h"
#pragma hdrstop
#include "prefix.h"
#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, RxNameCacheInitialize)
#pragma alloc_text(PAGE, RxNameCacheCreateEntry)
#pragma alloc_text(PAGE, RxNameCacheFetchEntry)
#pragma alloc_text(PAGE, RxNameCacheCheckEntry)
#pragma alloc_text(PAGE, RxNameCacheActivateEntry)
#pragma alloc_text(PAGE, RxNameCacheExpireEntry)
#pragma alloc_text(PAGE, RxNameCacheFreeEntry)
#pragma alloc_text(PAGE, RxNameCacheFinalize)
#pragma alloc_text(PAGE, RxNameCacheExpireEntryWithShortName)
#endif
#define Dbg (DEBUG_TRACE_NAMECACHE)
VOID RxNameCacheInitialize( IN PNAME_CACHE_CONTROL NameCacheCtl, IN ULONG MRxNameCacheSize, IN ULONG MaximumEntries )
/*++
Routine Description:
This routine initializes a NAME_CACHE structure.
Arguments:
NameCacheCtl - pointer to the NAME_CACHE_CONTROL from which to allocate the entry.
MRxNameCacheSize - The size in bytes of the mini-rdr portion of the name cache entry.
MaximumEntries - The maximum number of entries that will ever be allocated. E.g. This prevents an errant program which opens tons of files with bad names from chewing up paged pool.
Return Value:
None.
--*/ {
PAGED_CODE();
ExInitializeFastMutex(&NameCacheCtl->NameCacheLock);
InitializeListHead(&NameCacheCtl->ActiveList); InitializeListHead(&NameCacheCtl->FreeList);
NameCacheCtl->NumberActivates = 0; NameCacheCtl->NumberChecks = 0; NameCacheCtl->NumberNameHits = 0; NameCacheCtl->NumberNetOpsSaved = 0; NameCacheCtl->EntryCount = 0; NameCacheCtl->MaximumEntries = MaximumEntries; NameCacheCtl->MRxNameCacheSize = MRxNameCacheSize;
return; }
PNAME_CACHE RxNameCacheCreateEntry ( IN PNAME_CACHE_CONTROL NameCacheCtl, IN PUNICODE_STRING Name, IN BOOLEAN CaseInsensitive ) /*++
Routine Description:
This routine allocates and initializes a NAME_CACHE structure with the given name string, Lifetime (in seconds) and MRxContext. It returns a pointer to the name cache structure or NULL if no entry was available. It is expected that the caller will then initialize any additional mini-rdr portion of the name cache context and then put the entry on the name cache active list by calling RxNameCacheActivateEntry().
Arguments:
NameCacheCtl - pointer to the NAME_CACHE_CONTROL from which to allocate the entry.
Name - A pointer to the unicode name string to initialize the the entry with.
CaseInsensitive - True if need case insensitive compare on name.
Return Value:
PNAME_CACHE - returns a pointer to the newly allocated NAME_CACHE struct or NULL if allocation fails.
--*/ { LONG i; PNAME_CACHE *NameCacheArray; PNAME_CACHE NameCache; ULONG NameCacheSize;
PAGED_CODE();
RxDbgTrace( +1, Dbg, ("RxNameCacheCreateEntry: %wZ\n", Name )); //
// Grab an entry off the free list.
//
ExAcquireFastMutex(&NameCacheCtl->NameCacheLock);
if (!IsListEmpty(&NameCacheCtl->FreeList)) { NameCache = (PNAME_CACHE) RemoveHeadList(&NameCacheCtl->FreeList); } else { NameCache = NULL; }
ExReleaseFastMutex(&NameCacheCtl->NameCacheLock);
if (NameCache != NULL) { NameCache = CONTAINING_RECORD(NameCache, NAME_CACHE, Link); RxDbgTrace(0, Dbg, ("took from free list\n"));
} else { //
// Didn't get an entry off the free list, allocate one.
// Don't exceed Max but we could go over a little if multiple threads
// are allocating.
//
if (NameCacheCtl->EntryCount < NameCacheCtl->MaximumEntries) {
NameCacheSize = QuadAlign(sizeof(NAME_CACHE)) + QuadAlign(NameCacheCtl->MRxNameCacheSize); NameCache = RxAllocatePoolWithTag( PagedPool, NameCacheSize, RX_NAME_CACHE_POOLTAG);
if (NameCache != NULL) { //
// Init standard header fields, bump entry count & setup
// mini-rdr context extension.
//
ZeroAndInitializeNodeType( NameCache, RDBSS_NTC_STORAGE_TYPE_UNKNOWN, (NODE_BYTE_SIZE) NameCacheSize);
InterlockedIncrement(&NameCacheCtl->EntryCount);
NameCache->Name.Buffer = NULL; NameCache->Name.Length = 0; NameCache->Name.MaximumLength = 0;
if (NameCacheCtl->MRxNameCacheSize > 0) { NameCache->ContextExtension = (PBYTE)NameCache + QuadAlign(sizeof(NAME_CACHE)); RxDbgTrace(0, Dbg, ("allocated new entry\n")); } } }
//
// If still no entry then bag it.
//
if (NameCache == NULL) { RxDbgTrace(-1, Dbg, ("Fail no entry allocated!\n")); return NULL; }
}
//
// If name won't fit in current string, free it and allocate new string.
//
if (Name->Length > NameCache->Name.MaximumLength) { if (NameCache->Name.Buffer != NULL) { RxFreePool(NameCache->Name.Buffer); }
if (Name->Length > 0) { NameCache->Name.Buffer = RxAllocatePoolWithTag( PagedPool, (ULONG) Name->Length, RX_NAME_CACHE_POOLTAG);
} else { NameCache->Name.Buffer = NULL; }
if (Name->Length > 0 && NameCache->Name.Buffer == NULL) { //
// if didn't get the storage. Zero the string length and put entry
// back on the free list. Otherwise save allocation in max length.
//
NameCache->Name.Length = 0; NameCache->Name.MaximumLength = 0;
ExAcquireFastMutex(&NameCacheCtl->NameCacheLock); InsertHeadList(&NameCacheCtl->FreeList, &NameCache->Link); ExReleaseFastMutex(&NameCacheCtl->NameCacheLock);
RxDbgTrace(-1, Dbg, ("Fail no pool for name!\n")); return NULL; } else { NameCache->Name.MaximumLength = Name->Length; } }
//
// Save the name & length. Set the case matching flag. Set the hash field.
//
NameCache->Name.Length = Name->Length; NameCache->CaseInsensitive = CaseInsensitive;
if (Name->Length > 0) { RtlMoveMemory(NameCache->Name.Buffer, Name->Buffer, Name->Length); NameCache->HashValue = RxTableComputeHashValue(&NameCache->Name); }else { NameCache->HashValue = 0; }
RxDbgTrace(-1, Dbg, ("Success!\n")); return NameCache;
}
PNAME_CACHE RxNameCacheFetchEntry ( IN PNAME_CACHE_CONTROL NameCacheCtl, IN PUNICODE_STRING Name ) /*++
Routine Description:
This routine looks for a match in the name cache for Name. If found the entry is removed from the Name Cache active list and a pointer to the NAME_CACHE struct is returned. Otherwise NULL is returned. The entry is removed to avoid problems with another thread trying to update the same entry or observing that it expired and putting it on the free list. We could get multiple entries with the same name by different threads but eventually they will expire.
If a matching entry is found no check is made for expiration. That is left to the caller since it is likely the caller would want to take a special action.
Arguments:
NameCacheCtl - pointer to the NAME_CACHE_CONTROL to scan.
Name - A pointer to the unicode name string to scan for.
Return Value:
PNAME_CACHE - returns a pointer to the NAME_CACHE struct if found or NULL.
Side Effects:
As the active list is scanned any non-matching entries that have expired are put on the free list.
--*/ { PNAME_CACHE NameCache = NULL; PLIST_ENTRY pListEntry; PLIST_ENTRY ExpiredEntry; ULONG HashValue; LARGE_INTEGER CurrentTime;
PAGED_CODE();
RxDbgTrace( 0, Dbg, ("RxNameCacheFetchEntry: Lookup %wZ\n", Name ));
if (Name->Length > 0) { HashValue = RxTableComputeHashValue(Name); } else { HashValue = 0; }
KeQueryTickCount( &CurrentTime );
NameCacheCtl->NumberChecks += 1; //
// Get the lock and scan the active list.
//
ExAcquireFastMutex(&NameCacheCtl->NameCacheLock);
pListEntry = NameCacheCtl->ActiveList.Flink;
while (pListEntry != &NameCacheCtl->ActiveList) {
NameCache = (PNAME_CACHE) CONTAINING_RECORD(pListEntry, NAME_CACHE, Link); //
// Do initial match on the hash value and the length. Then do full string.
//
if ((NameCache->HashValue == HashValue) && (Name->Length == NameCache->Name.Length)) {
if (Name->Length == 0 || RtlEqualUnicodeString( Name, &NameCache->Name, NameCache->CaseInsensitive) ) { //
// Found a match.
//
NameCacheCtl->NumberNameHits += 1; break; } } //
// No match. If the entry is expired, put it on the free list.
//
ExpiredEntry = pListEntry; pListEntry = pListEntry->Flink;
if (CurrentTime.QuadPart >= NameCache->ExpireTime.QuadPart) { RemoveEntryList(ExpiredEntry); InsertHeadList(&NameCacheCtl->FreeList, ExpiredEntry); RxDbgTrace( 0, Dbg, ("RxNameCacheFetchEntry: Entry expired %wZ\n", &NameCache->Name )); }
NameCache = NULL; } //
// If we found something pull it off the active list and give it to caller.
//
if (NameCache != NULL) { RemoveEntryList(pListEntry); }
ExReleaseFastMutex(&NameCacheCtl->NameCacheLock);
if (NameCache != NULL) { RxDbgTrace( 0, Dbg, ("RxNameCacheFetchEntry: Entry found %wZ\n", &NameCache->Name )); }
return NameCache;
}
RX_NC_CHECK_STATUS RxNameCacheCheckEntry ( IN PNAME_CACHE NameCache, IN ULONG MRxContext ) /*++
Routine Description:
This routine checks a name cache entry for validity. A valid entry means that the lifetime has not expired and the MRxContext passes the equality check.
Arguments:
NameCache - pointer to NAME_CACHE struct to check.
MRxContext - A ULONG worth of mini-rdr supplied context for equality checking when making a valid entry check.
Return Value:
RX_NC_CHECK_STATUS: RX_NC_SUCCESS - The entry is valid RX_NC_TIME_EXPIRED - The Lifetime on the entry expired RX_NC_MRXCTX_FAIL - The MRxContext equality test failed
--*/ {
LARGE_INTEGER CurrentTime;
PAGED_CODE();
//
// Check for Mini-rdr context equality.
//
if (NameCache->Context != MRxContext) { RxDbgTrace( 0, Dbg, ("RxNameCacheCheckEntry: MRxContext_Fail %08lx,%08lx %wZ\n", NameCache->Context, MRxContext, &NameCache->Name ));
return RX_NC_MRXCTX_FAIL; }
//
// Check for lifetime expired.
//
KeQueryTickCount( &CurrentTime ); if (CurrentTime.QuadPart >= NameCache->ExpireTime.QuadPart) { RxDbgTrace( 0, Dbg, ("RxNameCacheCheckEntry: Expired %wZ\n", &NameCache->Name )); return RX_NC_TIME_EXPIRED; }
RxDbgTrace( 0, Dbg, ("RxNameCacheCheckEntry: Success %wZ\n", &NameCache->Name )); return RX_NC_SUCCESS;
}
VOID RxNameCacheExpireEntry( IN PNAME_CACHE_CONTROL NameCacheCtl, IN PNAME_CACHE NameCache ) /*++
Routine Description:
This routine puts the entry on the free list.
Arguments:
NameCacheCtl - pointer to the NAME_CACHE_CONTROL on which to activate the entry.
NameCache - pointer to NAME_CACHE struct to activate.
Return Value:
None.
Assumes:
The name cache entry is not on either the free or active list.
--*/ { PAGED_CODE();
RxDbgTrace( 0, Dbg, ("RxNameCacheExpireEntry: %wZ\n", &NameCache->Name ));
//
// Put the entry on free list for recycle.
//
ExAcquireFastMutex(&NameCacheCtl->NameCacheLock); InsertHeadList(&NameCacheCtl->FreeList, &NameCache->Link); ExReleaseFastMutex(&NameCacheCtl->NameCacheLock);
//
// Update stats.
//
NameCacheCtl->NumberActivates -= 1;
return; }
VOID RxNameCacheExpireEntryWithShortName ( IN PNAME_CACHE_CONTROL NameCacheCtl, IN PUNICODE_STRING Name ) /*++
Routine Description:
This routine expires all the name cache whose name prefix matches the given short file name.
Arguments:
NameCacheCtl - pointer to the NAME_CACHE_CONTROL to scan.
Name - A pointer to the unicode name string to scan for.
Return Value:
PNAME_CACHE - returns a pointer to the NAME_CACHE struct if found or NULL.
Side Effects:
As the active list is scanned any non-matching entries that have expired are put on the free list.
--*/ { PNAME_CACHE NameCache = NULL; PLIST_ENTRY pListEntry; PLIST_ENTRY ExpiredEntry; ULONG HashValue; LARGE_INTEGER CurrentTime;
PAGED_CODE();
RxDbgTrace( 0, Dbg, ("RxNameCacheFetchEntry: Lookup %wZ\n", Name ));
KeQueryTickCount( &CurrentTime );
NameCacheCtl->NumberChecks += 1; //
// Get the lock and scan the active list.
//
ExAcquireFastMutex(&NameCacheCtl->NameCacheLock);
pListEntry = NameCacheCtl->ActiveList.Flink;
while (pListEntry != &NameCacheCtl->ActiveList) { USHORT SavedNameLength;
NameCache = (PNAME_CACHE) CONTAINING_RECORD(pListEntry, NAME_CACHE, Link);
ExpiredEntry = pListEntry; pListEntry = pListEntry->Flink;
//
// Do initial match on the hash value and the length. Then do full string.
//
if (Name->Length <= NameCache->Name.Length) { SavedNameLength = NameCache->Name.Length; NameCache->Name.Length = Name->Length;
if (Name->Length == 0 || RtlEqualUnicodeString( Name, &NameCache->Name, NameCache->CaseInsensitive) ) { //
// Found a match.
//
RemoveEntryList(ExpiredEntry); InsertHeadList(&NameCacheCtl->FreeList, ExpiredEntry); RxDbgTrace( 0, Dbg, ("RxNameCacheExpireEntryWithShortName: Entry expired %wZ\n", &NameCache->Name ));
continue; }
NameCache->Name.Length = SavedNameLength; } }
ExReleaseFastMutex(&NameCacheCtl->NameCacheLock); }
VOID RxNameCacheActivateEntry ( IN PNAME_CACHE_CONTROL NameCacheCtl, IN PNAME_CACHE NameCache, IN ULONG LifeTime, IN ULONG MRxContext ) /*++
Routine Description:
This routine takes a name cache entry and updates the expiration time and the mini rdr context. It then puts the entry on the active list.
Arguments:
NameCacheCtl - pointer to the NAME_CACHE_CONTROL on which to activate the entry.
NameCache - pointer to NAME_CACHE struct to activate.
LifeTime - The valid lifetime of the cache entry (in seconds). A lifetime of zero means leave current value unchanged. This is for reactivations after a match where you want the original lifetime preserved.
MRxContext - A ULONG worth of mini-rdr supplied context for equality checking when making a valid entry check. An MRxContext of zero means leave current value unchanged. This is for reactivations after a match where you want the original MRxContext preserved.
Return Value:
None.
Assumes:
The name cache entry is not on either the free or active list.
--*/ { LARGE_INTEGER CurrentTime;
PAGED_CODE();
RxDbgTrace( 0, Dbg, ("RxNameCacheActivateEntry: %wZ\n", &NameCache->Name )); //
// Set new expiration time on the entry and save the mini-rdr context.
// A lifetime of zero or a MRxContext of zero implies leave value unchanged.
//
if (LifeTime != 0) { KeQueryTickCount( &CurrentTime ); NameCache->ExpireTime.QuadPart = CurrentTime.QuadPart + (LONGLONG) ((LifeTime * 10*1000*1000) / KeQueryTimeIncrement()); }
if (MRxContext != 0) { NameCache->Context = MRxContext; }
//
// Put the entry on active list.
//
ExAcquireFastMutex(&NameCacheCtl->NameCacheLock); InsertHeadList(&NameCacheCtl->ActiveList, &NameCache->Link); ExReleaseFastMutex(&NameCacheCtl->NameCacheLock);
//
// Update stats.
//
NameCacheCtl->NumberActivates += 1;
return; }
VOID RxNameCacheFreeEntry ( IN PNAME_CACHE_CONTROL NameCacheCtl, IN PNAME_CACHE NameCache ) /*++
Routine Description:
This routine releases the storage for a name cache entry and decrements the count of name cache entries for this name cache.
Arguments:
NameCacheCtl - pointer to the NAME_CACHE_CONTROL for the name cache.
NameCache - pointer to the NAME_CACHE struct to free.
Return Value:
None.
Assumes:
The name cache entry is not on either the free or active list.
--*/ { PAGED_CODE();
RxDbgTrace( 0, Dbg, ("RxNameCacheFreeEntry: %wZ\n", &NameCache->Name )); //
// Release storage for name
//
if (NameCache->Name.Buffer != NULL) { RxFreePool(NameCache->Name.Buffer); } //
// Release storage for NAME_CACHE entry (includes context ext., if any)
//
RxFreePool(NameCache);
InterlockedDecrement(&NameCacheCtl->EntryCount);
return; }
VOID RxNameCacheFinalize ( IN PNAME_CACHE_CONTROL NameCacheCtl ) /*++
Routine Description:
This routine releases the storage for all the name cache entries.
Arguments:
NameCacheCtl - pointer to the NAME_CACHE_CONTROL for the name cache.
Return Value:
None.
--*/ { PNAME_CACHE NameCache; PLIST_ENTRY pListEntry;
PAGED_CODE();
//
// Get the lock and remove entries from the active list.
//
ExAcquireFastMutex(&NameCacheCtl->NameCacheLock);
while (!IsListEmpty(&NameCacheCtl->ActiveList)) {
pListEntry = RemoveHeadList(&NameCacheCtl->ActiveList); NameCache = (PNAME_CACHE) CONTAINING_RECORD(pListEntry, NAME_CACHE, Link);
RxNameCacheFreeEntry(NameCacheCtl, NameCache); } //
// scan free list and remove entries.
//
while (!IsListEmpty(&NameCacheCtl->FreeList)) {
pListEntry = RemoveHeadList(&NameCacheCtl->FreeList); NameCache = (PNAME_CACHE) CONTAINING_RECORD(pListEntry, NAME_CACHE, Link);
RxNameCacheFreeEntry(NameCacheCtl, NameCache); }
ExReleaseFastMutex(&NameCacheCtl->NameCacheLock);
//
// At this point the entry count should be zero. If not then there is
// a memory leak since someone didn't call free.
//
ASSERT(NameCacheCtl->EntryCount == 0);
return;
}
|