/*++ Copyright (c) 1989 Microsoft Corporation Module Name: blkdir.c Abstract: This module implements routines for managing cached directory names Author: Isaac Heizer Revision History: --*/ #include "precomp.h" #include "blkdir.tmh" #pragma hdrstop #define BugCheckFileId SRV_FILE_BLKDIR BOOLEAN SrvIsDirectoryCached ( IN PWORK_CONTEXT WorkContext, IN PUNICODE_STRING DirectoryName ) { PLIST_ENTRY listEntry; PCACHED_DIRECTORY cd; ULONG directoryNameHashValue; PCONNECTION connection = WorkContext->Connection; KIRQL oldIrql; LARGE_INTEGER timeNow; // // DirectoryName must point to memory in nonpaged pool, else we can't touch // it under spinlock control. If the incomming SMB is UNICODE, we know that // the name is in the smb buffer, and is therefore in nonpaged pool. Otherwise // we can't trust it and we're better off just not trying to cache it. // if( connection->CachedDirectoryCount == 0 || !SMB_IS_UNICODE( WorkContext ) ) { return FALSE; } KeQueryTickCount( &timeNow ); timeNow.LowPart -= (SrvFiveSecondTickCount >> 1 ); ACQUIRE_SPIN_LOCK( &connection->SpinLock, &oldIrql ); top: for ( listEntry = connection->CachedDirectoryList.Flink; listEntry != &connection->CachedDirectoryList; listEntry = listEntry->Flink ) { cd = CONTAINING_RECORD( listEntry, CACHED_DIRECTORY, ListEntry ); // // Is this element too old? // if( cd->TimeStamp < timeNow.LowPart ) { // // This element is more than 2.5 seconds old. Toss it out // RemoveEntryList( listEntry ); connection->CachedDirectoryCount--; DEALLOCATE_NONPAGED_POOL( cd ); goto top; } if( cd->Tid != WorkContext->TreeConnect->Tid ) { continue; } // // Is the requested entry a subdir of this cache entry? // if( DirectoryName->Length < cd->DirectoryName.Length && RtlCompareMemory( DirectoryName->Buffer, cd->DirectoryName.Buffer, DirectoryName->Length ) == DirectoryName->Length && cd->DirectoryName.Buffer[ DirectoryName->Length / sizeof( WCHAR ) ] == L'\\' ) { RELEASE_SPIN_LOCK( &connection->SpinLock, oldIrql ); return TRUE; // // Not a subdir -- is it an exact match? // } else if( DirectoryName->Length == cd->DirectoryName.Length && RtlCompareMemory( cd->DirectoryName.Buffer, DirectoryName->Buffer, DirectoryName->Length ) == DirectoryName->Length ) { RELEASE_SPIN_LOCK( &connection->SpinLock, oldIrql ); return TRUE; } } RELEASE_SPIN_LOCK( &connection->SpinLock, oldIrql ); return FALSE; } VOID SrvCacheDirectoryName ( IN PWORK_CONTEXT WorkContext, IN PUNICODE_STRING DirectoryName ) /*++ Routine Description: This routine remembers 'DirectoryName' for further fast processing of the CheckPath SMB Arguments: WorkContext - Pointer to the work context block DirectoryName - Fully canonicalized name of the directory we're caching ++*/ { CLONG blockLength; PCACHED_DIRECTORY cd; KIRQL oldIrql; PCONNECTION connection = WorkContext->Connection; PLIST_ENTRY listEntry; LARGE_INTEGER timeNow; USHORT tid; if( SrvMaxCachedDirectory == 0 ) { return; } // // DirectoryName must point to memory in nonpaged pool, else we can't touch // it under spinlock control. If the incomming SMB is UNICODE, we know that // the name is in the smb buffer, and is therefore in nonpaged pool. Otherwise // we can't trust it and we're better off just not trying to cache it. // if( !SMB_IS_UNICODE( WorkContext ) ) { return; } KeQueryTickCount( &timeNow ); timeNow.LowPart -= ( SrvFiveSecondTickCount >> 1 ); tid = WorkContext->TreeConnect->Tid; ACQUIRE_SPIN_LOCK( &connection->SpinLock, &oldIrql ); // // Search the directory cache and see if this directory is already cached. If so, // don't cache it again. // top: for ( listEntry = connection->CachedDirectoryList.Flink; listEntry != &connection->CachedDirectoryList; listEntry = listEntry->Flink ) { cd = CONTAINING_RECORD( listEntry, CACHED_DIRECTORY, ListEntry ); // // Is this element too old? // if( cd->TimeStamp < timeNow.LowPart ) { // // This element is more than 2.5 seconds old. Toss it out // RemoveEntryList( listEntry ); connection->CachedDirectoryCount--; DEALLOCATE_NONPAGED_POOL( cd ); goto top; } if( cd->Tid != tid ) { continue; } // // Is the new entry a subdir of this cache entry? // if( DirectoryName->Length < cd->DirectoryName.Length && RtlCompareMemory( DirectoryName->Buffer, cd->DirectoryName.Buffer, DirectoryName->Length ) == DirectoryName->Length && cd->DirectoryName.Buffer[ DirectoryName->Length / sizeof( WCHAR ) ] == L'\\' ) { // // It is a subdir -- no need to cache it again // RELEASE_SPIN_LOCK( &connection->SpinLock, oldIrql ); return; } // // Is the cache entry a subdir of the new entry? // if( cd->DirectoryName.Length < DirectoryName->Length && RtlCompareMemory( DirectoryName->Buffer, cd->DirectoryName.Buffer, cd->DirectoryName.Length ) == cd->DirectoryName.Length && DirectoryName->Buffer[ cd->DirectoryName.Length / sizeof( WCHAR ) ] == L'\\' ) { // // We can remove this entry // RemoveEntryList( listEntry ); connection->CachedDirectoryCount--; DEALLOCATE_NONPAGED_POOL( cd ); // // We want to cache this new longer entry // break; } // // Not a subdir -- is it an exact match? // if( cd->DirectoryName.Length == DirectoryName->Length && RtlCompareMemory( cd->DirectoryName.Buffer, DirectoryName->Buffer, DirectoryName->Length ) == DirectoryName->Length ) { // // This entry is already in the cache -- no need to recache // RELEASE_SPIN_LOCK( &connection->SpinLock, oldIrql ); return; } } // // This directory name is not already in the cache. So add it. // blockLength = sizeof( CACHED_DIRECTORY ) + DirectoryName->Length + sizeof(WCHAR); cd = ALLOCATE_NONPAGED_POOL( blockLength, BlockTypeCachedDirectory ); if( cd == NULL ) { RELEASE_SPIN_LOCK( &connection->SpinLock, oldIrql ); INTERNAL_ERROR( ERROR_LEVEL_EXPECTED, "SrvCacheDirectoryName: Unable to allocate %d bytes from pool", blockLength, NULL ); return; } cd->Type = BlockTypeCachedDirectory; cd->State = BlockStateActive; cd->Size = (USHORT)blockLength; // cd->ReferenceCount = 1; // not used // // Set the timestamp of this entry. Remember, we subtracted // ticks up above from timeNow -- put them back in now. // cd->TimeStamp = timeNow.LowPart + ( SrvFiveSecondTickCount >> 1 ); // // Store the directory name as it was passed into us // cd->DirectoryName.Length = DirectoryName->Length; cd->DirectoryName.MaximumLength = (USHORT)DirectoryName->MaximumLength; cd->DirectoryName.Buffer = (PWCH)(cd + 1); RtlCopyMemory( cd->DirectoryName.Buffer, DirectoryName->Buffer, DirectoryName->Length ); cd->Tid = tid; InsertHeadList( &connection->CachedDirectoryList, &cd->ListEntry ); // // Check the number of elements in the cache. If getting too large, close oldest one. // if( connection->CachedDirectoryCount++ < SrvMaxCachedDirectory ) { RELEASE_SPIN_LOCK( &connection->SpinLock, oldIrql ); return; } // // Remove the last entry from the cache // cd = CONTAINING_RECORD( connection->CachedDirectoryList.Blink, CACHED_DIRECTORY, ListEntry ); RemoveEntryList( &cd->ListEntry ); connection->CachedDirectoryCount--; RELEASE_SPIN_LOCK( &connection->SpinLock, oldIrql ); DEALLOCATE_NONPAGED_POOL( cd ); return; } VOID SrvRemoveCachedDirectoryName( IN PWORK_CONTEXT WorkContext, IN PUNICODE_STRING DirectoryName ) { PLIST_ENTRY listEntry; PCACHED_DIRECTORY cd; ULONG directoryNameHashValue; PCONNECTION connection = WorkContext->Connection; KIRQL oldIrql; USHORT tid; if( connection->CachedDirectoryCount == 0 ) { return; } // // DirectoryName must point to memory in nonpaged pool, else we can't touch // it under spinlock control. If the incomming SMB is UNICODE, we know that // the name is in the smb buffer, and is therefore in nonpaged pool. Otherwise // we can't trust it and we're better off just not trying to cache it. // if( !SMB_IS_UNICODE( WorkContext ) ) { return; } COMPUTE_STRING_HASH( DirectoryName, &directoryNameHashValue ); tid = WorkContext->TreeConnect->Tid; ACQUIRE_SPIN_LOCK( &connection->SpinLock, &oldIrql ); for ( listEntry = connection->CachedDirectoryList.Flink; listEntry != &connection->CachedDirectoryList; listEntry = listEntry->Flink ) { cd = CONTAINING_RECORD( listEntry, CACHED_DIRECTORY, ListEntry ); // // See if this entry is an exact match for what was requested // if( cd->DirectoryName.Length == DirectoryName->Length && cd->Tid == tid && RtlCompareMemory( cd->DirectoryName.Buffer, DirectoryName->Buffer, DirectoryName->Length ) == DirectoryName->Length ) { // // Remove this entry from the list and adjust the count // RemoveEntryList( &cd->ListEntry ); connection->CachedDirectoryCount--; ASSERT( (LONG)connection->CachedDirectoryCount >= 0 ); RELEASE_SPIN_LOCK( &connection->SpinLock, oldIrql ); DEALLOCATE_NONPAGED_POOL( cd ); return; } } RELEASE_SPIN_LOCK( &connection->SpinLock, oldIrql ); return; } VOID SrvCloseCachedDirectoryEntries( IN PCONNECTION Connection ) /*++ Routine Description: This routine closes all the cached directory entries on the connection Arguments: Connection - Pointer to the connection structure having the cache ++*/ { KIRQL oldIrql; PCACHED_DIRECTORY cd; ACQUIRE_SPIN_LOCK( &Connection->SpinLock, &oldIrql ); while( Connection->CachedDirectoryCount > 0 ) { cd = CONTAINING_RECORD( Connection->CachedDirectoryList.Flink, CACHED_DIRECTORY, ListEntry ); RemoveEntryList( &cd->ListEntry ); Connection->CachedDirectoryCount--; DEALLOCATE_NONPAGED_POOL( cd ); } RELEASE_SPIN_LOCK( &Connection->SpinLock, oldIrql ); }