Leaked source code of windows server 2003
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.
 
 
 
 
 
 

3502 lines
95 KiB

/*++
Copyright (c) 1998-2002 Microsoft Corporation
Module Name:
cache.c
Abstract:
Contains the HTTP response cache logic.
Author:
Michael Courage (mcourage) 17-May-1999
Revision History:
--*/
#include "precomp.h"
#include "cachep.h"
BOOLEAN g_InitUriCacheCalled;
//
// Global hash table
//
HASHTABLE g_UriCacheTable;
LIST_ENTRY g_ZombieListHead;
UL_URI_CACHE_CONFIG g_UriCacheConfig;
UL_URI_CACHE_STATS g_UriCacheStats;
UL_SPIN_LOCK g_UriCacheSpinLock;
//
// Turn on/off cache at runtime
//
LONG g_CacheMemEnabled = TRUE;
#ifdef ALLOC_PRAGMA
#pragma alloc_text( INIT, UlInitializeUriCache )
#pragma alloc_text( PAGE, UlTerminateUriCache )
#pragma alloc_text( PAGE, UlCheckCachePreconditions )
#pragma alloc_text( PAGE, UlCheckCacheResponseConditions )
#pragma alloc_text( PAGE, UlCheckoutUriCacheEntry )
#pragma alloc_text( PAGE, UlCheckinUriCacheEntry )
#pragma alloc_text( PAGE, UlFlushCache )
#pragma alloc_text( PAGE, UlpFlushFilterAll )
#pragma alloc_text( PAGE, UlFlushCacheByProcess )
#pragma alloc_text( PAGE, UlpFlushFilterProcess )
#pragma alloc_text( PAGE, UlFlushCacheByUri )
#pragma alloc_text( PAGE, UlpFlushUri )
#pragma alloc_text( PAGE, UlAddCacheEntry )
#pragma alloc_text( PAGE, UlpFilteredFlushUriCache )
#pragma alloc_text( PAGE, UlpFilteredFlushUriCacheInline )
#pragma alloc_text( PAGE, UlpFilteredFlushUriCacheWorker )
#pragma alloc_text( PAGE, UlpAddZombie )
#pragma alloc_text( PAGE, UlpClearZombieList )
#pragma alloc_text( PAGE, UlpDestroyUriCacheEntry )
#pragma alloc_text( PAGE, UlPeriodicCacheScavenger )
#pragma alloc_text( PAGE, UlpFlushFilterPeriodicScavenger )
#pragma alloc_text( PAGE, UlTrimCache )
#pragma alloc_text( PAGE, UlpFlushFilterTrimCache )
#pragma alloc_text( PAGE, UlpQueryTranslateHeader )
#pragma alloc_text( PAGE, UlpQueryExpectHeader )
#pragma alloc_text( PAGE, UlAddFragmentToCache )
#pragma alloc_text( PAGE, UlReadFragmentFromCache )
#pragma alloc_text( PAGE, UlpCreateFragmentCacheEntry )
#pragma alloc_text( PAGE, UlAllocateCacheEntry )
#pragma alloc_text( PAGE, UlAddCacheEntry )
#pragma alloc_text( PAGE, UlDisableCache )
#pragma alloc_text( PAGE, UlEnableCache )
#endif // ALLOC_PRAGMA
#if 0
NOT PAGEABLE -- UlpCheckTableSpace
NOT PAGEABLE -- UlpCheckSpaceAndAddEntryStats
NOT PAGEABLE -- UlpRemoveEntryStats
#endif
/***************************************************************************++
Routine Description:
Performs global initialization of the URI cache.
Return Value:
NTSTATUS - Completion status.
--***************************************************************************/
NTSTATUS
UlInitializeUriCache(
PUL_CONFIG pConfig
)
{
NTSTATUS Status = STATUS_SUCCESS;
//
// Sanity check.
//
PAGED_CODE();
ASSERT( !g_InitUriCacheCalled );
UlTrace(URI_CACHE, ("Http!UlInitializeUriCache\n"));
if ( !g_InitUriCacheCalled )
{
PUL_URI_CACHE_CONFIG pUriConfig = &pConfig->UriConfig;
g_UriCacheConfig.EnableCache = pUriConfig->EnableCache;
g_UriCacheConfig.MaxCacheUriCount = pUriConfig->MaxCacheUriCount;
g_UriCacheConfig.MaxCacheMegabyteCount =
pUriConfig->MaxCacheMegabyteCount;
g_UriCacheConfig.MaxCacheByteCount =
(((ULONGLONG) g_UriCacheConfig.MaxCacheMegabyteCount)
<< MEGABYTE_SHIFT);
//
// Don't want to scavenge more than once every ten seconds.
// In particular, do not want to scavenge every 0 seconds, as the
// machine will become completely unresponsive.
//
g_UriCacheConfig.ScavengerPeriod =
max(pUriConfig->ScavengerPeriod, 10);
g_UriCacheConfig.MaxUriBytes = pUriConfig->MaxUriBytes;
g_UriCacheConfig.HashTableBits = pUriConfig->HashTableBits;
RtlZeroMemory(&g_UriCacheStats, sizeof(g_UriCacheStats));
InitializeListHead(&g_ZombieListHead);
UlInitializeSpinLock( &g_UriCacheSpinLock, "g_UriCacheSpinLock" );
if (g_UriCacheConfig.EnableCache)
{
Status = UlInitializeResource(
&g_pUlNonpagedData->UriZombieResource,
"UriZombieResource",
0,
UL_ZOMBIE_RESOURCE_TAG
);
if (NT_SUCCESS(Status))
{
Status = UlInitializeHashTable(
&g_UriCacheTable,
PagedPool,
g_UriCacheConfig.HashTableBits
);
if (NT_SUCCESS(Status))
{
ASSERT(IS_VALID_HASHTABLE(&g_UriCacheTable));
Status = UlInitializeScavengerThread();
g_InitUriCacheCalled = TRUE;
}
}
else
{
UlDeleteResource(&g_pUlNonpagedData->UriZombieResource);
}
}
else
{
UlTrace(URI_CACHE, ("URI Cache disabled.\n"));
g_InitUriCacheCalled = TRUE;
}
}
else
{
UlTrace(URI_CACHE, ("URI CACHE INITIALIZED TWICE!\n"));
}
return Status;
} // UlInitializeUriCache
/***************************************************************************++
Routine Description:
Performs global termination of the URI cache.
--***************************************************************************/
VOID
UlTerminateUriCache(
VOID
)
{
NTSTATUS Status;
//
// Sanity check.
//
PAGED_CODE();
UlTrace(URI_CACHE, ("Http!UlTerminateUriCache\n"));
if (g_InitUriCacheCalled && g_UriCacheConfig.EnableCache)
{
// Must terminate the scavenger before destroying the hash table
UlTerminateScavengerThread();
UlTerminateHashTable(&g_UriCacheTable);
Status = UlDeleteResource(&g_pUlNonpagedData->UriZombieResource);
ASSERT(NT_SUCCESS(Status));
}
g_InitUriCacheCalled = FALSE;
} // UlTerminateUriCache
/***************************************************************************++
Routine Description:
This routine checks a request (and its connection) to see if it's
ok to serve this request from the cache. Basically we only accept
simple GET requests with no conditional headers.
Arguments:
pHttpConn - The connection to be checked
Return Value:
BOOLEAN - True if it's ok to serve from cache
--***************************************************************************/
BOOLEAN
UlCheckCachePreconditions(
PUL_INTERNAL_REQUEST pRequest,
PUL_HTTP_CONNECTION pHttpConn
)
{
URI_PRECONDITION Precondition = URI_PRE_OK;
UNREFERENCED_PARAMETER(pHttpConn);
//
// Sanity check
//
PAGED_CODE();
ASSERT( UL_IS_VALID_HTTP_CONNECTION(pHttpConn) );
ASSERT( UL_IS_VALID_INTERNAL_REQUEST(pRequest) );
if (!g_UriCacheConfig.EnableCache)
{
Precondition = URI_PRE_DISABLED;
}
else if (pRequest->ParseState != ParseDoneState)
{
Precondition = URI_PRE_ENTITY_BODY;
}
else if (pRequest->Verb != HttpVerbGET)
{
Precondition = URI_PRE_VERB;
}
else if (HTTP_NOT_EQUAL_VERSION(pRequest->Version, 1, 1)
&& HTTP_NOT_EQUAL_VERSION(pRequest->Version, 1, 0))
{
Precondition = URI_PRE_PROTOCOL;
}
// check for Translate: f (DAV)
else if ( UlpQueryTranslateHeader(pRequest) )
{
Precondition = URI_PRE_TRANSLATE;
}
// check for non-100-continue expectation
else if ( !UlpQueryExpectHeader(pRequest) )
{
Precondition = URI_PRE_EXPECTATION_FAILED;
}
// check for Authorization header
else if (pRequest->HeaderValid[HttpHeaderAuthorization])
{
Precondition = URI_PRE_AUTHORIZATION;
}
//
// check for some of the If-* headers
// NOTE: See UlpCheckCacheControlHeaders for handling of other If-* headers
//
else if (pRequest->HeaderValid[HttpHeaderIfRange])
{
Precondition = URI_PRE_CONDITIONAL;
}
// CODEWORK: check for other evil headers
else if (pRequest->HeaderValid[HttpHeaderRange])
{
Precondition = URI_PRE_OTHER_HEADER;
}
UlTrace(URI_CACHE,
("Http!UlCheckCachePreconditions(req = %p, '%ls', httpconn = %p)\n"
" OkToServeFromCache = %d, Precondition = %d\n",
pRequest,
pRequest->CookedUrl.pUrl,
pHttpConn,
(URI_PRE_OK == Precondition) ? 1 : 0,
Precondition
));
return (BOOLEAN) (URI_PRE_OK == Precondition);
} // UlCheckCachePreconditions
/***************************************************************************++
Routine Description:
This routine checks a response to see if it's cacheable. Basically
we'll take it if:
* the cache policy is right
* the size is small enough
* there is room in the cache
* we get the response all at once
Arguments:
pHttpConn - The connection to be checked
Return Value:
BOOLEAN - True if it's ok to serve from cache
--***************************************************************************/
BOOLEAN
UlCheckCacheResponseConditions(
PUL_INTERNAL_REQUEST pRequest,
PUL_INTERNAL_RESPONSE pResponse,
ULONG Flags,
HTTP_CACHE_POLICY CachePolicy
)
{
URI_PRECONDITION Precondition = URI_PRE_OK;
//
// Sanity check
//
PAGED_CODE();
ASSERT( UL_IS_VALID_INTERNAL_REQUEST(pRequest) );
ASSERT( UL_IS_VALID_INTERNAL_RESPONSE(pResponse) );
if (pRequest->CachePreconditions == FALSE) {
Precondition = URI_PRE_REQUEST;
}
// check policy
else if (CachePolicy.Policy == HttpCachePolicyNocache) {
Precondition = URI_PRE_POLICY;
}
// check if Date: header is valid (can affect If-Modified-Since handling)
else if (!pResponse->GenDateHeader || (0L == pResponse->CreationTime.QuadPart)) {
Precondition = URI_PRE_PROTOCOL;
}
// check size of response
else if ((pResponse->ResponseLength - pResponse->HeaderLength) >
g_UriCacheConfig.MaxUriBytes) {
Precondition = URI_PRE_SIZE;
}
// check if the header length exceeds the limit
else if (pResponse->HeaderLength > g_UlMaxFixedHeaderSize) {
Precondition = URI_PRE_SIZE;
}
// check for full response
else if (Flags & HTTP_SEND_RESPONSE_FLAG_MORE_DATA) {
Precondition = URI_PRE_FRAGMENT;
}
// check available cache table space
else if (!UlpCheckTableSpace(pResponse->ResponseLength)) {
Precondition = URI_PRE_NOMEMORY;
}
// Check for bogus responses
else if ((pResponse->ResponseLength < 1) || (pResponse->ChunkCount < 2)) {
Precondition = URI_PRE_BOGUS;
}
// FUTURE: check if multiple Content-Encodings are applied
// else if ( /* multiple encodings */ )
// {
// Precondition = URI_PRE_BOGUS;
// }
UlTrace(URI_CACHE,
("Http!UlCheckCacheResponseConditions("
"pRequest = %p, '%ls', pResponse = %p)\n"
" OkToCache = %d, Precondition = %d\n",
pRequest,
pRequest->CookedUrl.pUrl,
pResponse,
(URI_PRE_OK == Precondition),
Precondition
));
return (BOOLEAN) (URI_PRE_OK == Precondition);
} // UlCheckCacheResponseConditions
/***************************************************************************++
Routine Description:
This routine does a cache lookup to see if there is a valid entry
corresponding to the request URI.
Arguments:
pSearchKey - The -extended or normal- Uri Key
Return Value:
PUL_URI_CACHE_ENTRY - Pointer to the entry, if found. NULL otherwise.
--***************************************************************************/
PUL_URI_CACHE_ENTRY
UlCheckoutUriCacheEntry(
PURI_SEARCH_KEY pSearchKey
)
{
PUL_URI_CACHE_ENTRY pUriCacheEntry = NULL;
//
// Sanity check
//
PAGED_CODE();
ASSERT(!g_UriCacheConfig.EnableCache
|| IS_VALID_HASHTABLE(&g_UriCacheTable));
ASSERT(IS_VALID_URI_SEARCH_KEY(pSearchKey));
pUriCacheEntry = UlGetFromHashTable(
&g_UriCacheTable,
pSearchKey
);
if (pUriCacheEntry != NULL)
{
ASSERT( IS_VALID_URI_CACHE_ENTRY(pUriCacheEntry) );
//
// see if entry has expired; if so, check it right back in
// without touching the stats. We expect the scavenger to
// deal with flushing this entry the next time it runs, so
// we can defer the flush.
//
if ( HttpCachePolicyTimeToLive == pUriCacheEntry->CachePolicy.Policy )
{
LARGE_INTEGER Now;
KeQuerySystemTime(&Now);
if ( Now.QuadPart > pUriCacheEntry->ExpirationTime.QuadPart )
{
UlTrace(URI_CACHE,
("Http!UlCheckoutUriCacheEntry: pUriCacheEntry %p is EXPIRED\n",
pUriCacheEntry
));
UlCheckinUriCacheEntry(pUriCacheEntry);
pUriCacheEntry = NULL;
goto end;
}
}
pUriCacheEntry->HitCount++;
// reset scavenger counter
pUriCacheEntry->ScavengerTicks = 0;
UlTrace(URI_CACHE,
("Http!UlCheckoutUriCacheEntry(pUriCacheEntry %p, '%ls') "
"refcount = %d\n",
pUriCacheEntry, pUriCacheEntry->UriKey.pUri,
pUriCacheEntry->ReferenceCount
));
}
else
{
UlTrace(URI_CACHE,
("Http!UlCheckoutUriCacheEntry(failed: Token:'%ls' '%ls' )\n",
pSearchKey->Type == UriKeyTypeExtended ?
pSearchKey->ExKey.pToken :
L"",
pSearchKey->Type == UriKeyTypeExtended ?
pSearchKey->ExKey.pAbsPath :
pSearchKey->Key.pUri
));
}
end:
return pUriCacheEntry;
} // UlCheckoutUriCacheEntry
/***************************************************************************++
Routine Description:
Decrements the refcount on a cache entry. Cleans up non-cached
entries.
Arguments:
pUriCacheEntry - the entry to deref
--***************************************************************************/
VOID
UlCheckinUriCacheEntry(
PUL_URI_CACHE_ENTRY pUriCacheEntry
)
{
LONG ReferenceCount;
//
// Sanity check
//
PAGED_CODE();
ASSERT(!g_UriCacheConfig.EnableCache
|| IS_VALID_HASHTABLE(&g_UriCacheTable));
ASSERT( IS_VALID_URI_CACHE_ENTRY(pUriCacheEntry) );
UlTrace(URI_CACHE,
("Http!UlCheckinUriCacheEntry(pUriCacheEntry %p, '%ls')\n",
pUriCacheEntry, pUriCacheEntry->UriKey.pUri
));
//
// decrement count
//
ReferenceCount = DEREFERENCE_URI_CACHE_ENTRY(pUriCacheEntry, CHECKIN);
ASSERT(ReferenceCount >= 0);
} // UlCheckinUriCacheEntry
/***************************************************************************++
Routine Description:
Temporarly disables the indexing mechanism in CB Logging.
Removes all cache entries, unconditionally.
Writes a cache flush notification to the CB log file, and enables the
indexing mechanism back.
--***************************************************************************/
VOID
UlFlushCache(
IN PUL_CONTROL_CHANNEL pControlChannel
)
{
//
// Sanity check
//
PAGED_CODE();
ASSERT(!g_UriCacheConfig.EnableCache
|| IS_VALID_HASHTABLE(&g_UriCacheTable));
//
// Caller needs to hold the CG Lock exclusive, the flushes needs to
// be serialized.
//
ASSERT(UlDbgResourceOwnedExclusive(
&g_pUlNonpagedData->ConfigGroupResource
));
//
// Nothing to do, if cache is disabled.
//
if (g_UriCacheConfig.EnableCache)
{
UlTrace(URI_CACHE,("Http!UlFlushCache()\n"));
// TODO: Need to notify every control channel
//
// This is to prevent any outstanding sends for the zombified
// cache entries to refer to the obsolete indexes, in case the
// sends get completed after we write the cache notification
// entry to the log file. The CB logging calls here must be
// preserved in this order and must be used while holding the
// CG lock. Do not release the lock acquire it again and call
// the other.
//
if (pControlChannel)
{
UlDisableIndexingForCacheHits(pControlChannel);
}
//
// Unconditionally zombifies all of the uri cache entries.
//
UlpFilteredFlushUriCache(UlpFlushFilterAll, NULL, NULL, 0);
//
// HandleFlush will enable the (CB Logging) indexing,
// once it is done writting the notification record.
//
if (pControlChannel)
{
UlHandleCacheFlushedNotification(pControlChannel);
}
}
} // UlFlushCache
/***************************************************************************++
Routine Description:
A filter for UlFlushCache. Called by UlpFilteredFlushUriCache.
Arguments:
pUriCacheEntry - the entry to check
pContext - ignored
--***************************************************************************/
UL_CACHE_PREDICATE
UlpFlushFilterAll(
IN PUL_URI_CACHE_ENTRY pUriCacheEntry,
IN PVOID pContext
)
{
PURI_FILTER_CONTEXT pUriFilterContext = (PURI_FILTER_CONTEXT) pContext;
//
// Sanity check
//
PAGED_CODE();
ASSERT( IS_VALID_URI_CACHE_ENTRY(pUriCacheEntry) );
ASSERT( pUriFilterContext != NULL
&& URI_FILTER_CONTEXT_POOL_TAG == pUriFilterContext->Signature
&& pUriFilterContext->pCallerContext == NULL );
UlTrace(URI_CACHE, (
"Http!UlpFlushFilterAll(pUriCacheEntry %p '%ls') refcount = %d\n",
pUriCacheEntry, pUriCacheEntry->UriKey.pUri,
pUriCacheEntry->ReferenceCount));
//
// Second BOOLEAN must * only * be true if the UlpFlushFilterAll is
// called by UlFlushCache (since it writes a binary log file record
// of flush).
//
return UlpZombifyEntry(
TRUE,
TRUE,
pUriCacheEntry,
pUriFilterContext
);
} // UlpFlushFilterAll
/***************************************************************************++
Routine Description:
Removes any cache entries that were created by the given process.
Arguments:
pProcess - a process that is going away
--***************************************************************************/
VOID
UlFlushCacheByProcess(
PUL_APP_POOL_PROCESS pProcess
)
{
//
// sanity check
//
PAGED_CODE();
ASSERT( IS_VALID_AP_PROCESS(pProcess) );
ASSERT(!g_UriCacheConfig.EnableCache
|| IS_VALID_HASHTABLE(&g_UriCacheTable));
if (g_UriCacheConfig.EnableCache)
{
UlTrace(URI_CACHE, (
"Http!UlFlushCacheByProcess(proc = %p)\n",
pProcess
));
UlpFilteredFlushUriCache(UlpFlushFilterProcess, pProcess, NULL, 0);
}
} // UlFlushCacheByProcess
/***************************************************************************++
Routine Description:
If recursive flag has been picked this function removes the cache entries
matching with the given prefix. (pUri)
Otherwise removes the specific URL from the cache.
Arguments:
pUri - the uri prefix to match against
Length - length of the prefix, in bytes
Flags - HTTP_FLUSH_RESPONSE_FLAG_RECURSIVE indicates a tree flush
pProcess - the process that made the call
--***************************************************************************/
VOID
UlFlushCacheByUri(
IN PWSTR pUri,
IN ULONG Length,
IN ULONG Flags,
IN PUL_APP_POOL_PROCESS pProcess
)
{
NTSTATUS Status;
BOOLEAN Recursive;
PWSTR pCopiedUri;
//
// Sanity check.
//
PAGED_CODE();
ASSERT( IS_VALID_AP_PROCESS(pProcess) );
ASSERT( !g_UriCacheConfig.EnableCache
|| IS_VALID_HASHTABLE(&g_UriCacheTable) );
if (!g_UriCacheConfig.EnableCache)
{
return;
}
Status = STATUS_SUCCESS;
Recursive = (BOOLEAN) (0 != (Flags & HTTP_FLUSH_RESPONSE_FLAG_RECURSIVE));
pCopiedUri = NULL;
UlTrace(URI_CACHE, (
"Http!UlFlushCacheByUri(\n"
" uri = '%S'\n"
" len = %d\n"
" flags = %08x, recursive = %d\n"
" proc = %p\n",
pUri,
Length,
Flags,
(int) Recursive,
pProcess
));
//
// Ensure pUri ends with a L'/' if Recursive flag is set.
//
if (Recursive &&
pUri[(Length-sizeof(WCHAR))/sizeof(WCHAR)] != L'/')
{
//
// Make a copy of the origianl URL and append a L'/' to it.
//
pCopiedUri = (PWSTR) UL_ALLOCATE_POOL(
PagedPool,
Length + sizeof(WCHAR) + sizeof(WCHAR),
UL_UNICODE_STRING_POOL_TAG
);
if (!pCopiedUri)
{
Status = STATUS_NO_MEMORY;
}
else
{
RtlCopyMemory(
pCopiedUri,
pUri,
Length
);
pCopiedUri[Length/sizeof(WCHAR)] = L'/';
pCopiedUri[(Length+sizeof(WCHAR))/sizeof(WCHAR)] = UNICODE_NULL;
pUri = pCopiedUri;
Length += sizeof(WCHAR);
}
}
if (NT_SUCCESS(Status))
{
if (Recursive)
{
//
// When the recursive flag is set, we are supposed
// to do prefix match with respect to provided URL.
// Any cache entry matches with the prefix will be
// flushed out from the cache.
//
UlpFilteredFlushUriCache(
UlpFlushFilterUriRecursive,
pProcess,
pUri,
Length
);
}
else
{
UlpFlushUri(
pUri,
Length,
pProcess
);
UlpClearZombieList();
}
}
if (pCopiedUri)
{
UL_FREE_POOL(pCopiedUri, UL_UNICODE_STRING_POOL_TAG);
}
} // UlFlushCacheByUri
/***************************************************************************++
Routine Description:
Removes a single URI from the table if the name and process match an
entry.
Arguments:
--***************************************************************************/
VOID
UlpFlushUri(
IN PWSTR pUri,
IN ULONG Length,
PUL_APP_POOL_PROCESS pProcess
)
{
PUL_URI_CACHE_ENTRY pUriCacheEntry = NULL;
URI_KEY Key;
//
// Sanity check
//
PAGED_CODE();
//
// find bucket
//
Key.Hash = HashRandomizeBits(HashStringNoCaseW(pUri, 0));
Key.Length = Length;
Key.pUri = pUri;
Key.pPath = NULL;
pUriCacheEntry = UlDeleteFromHashTable(&g_UriCacheTable, &Key, pProcess);
if (NULL != pUriCacheEntry)
{
ASSERT( IS_VALID_URI_CACHE_ENTRY(pUriCacheEntry) );
UlTrace(URI_CACHE, (
"Http!UlpFlushUri(pUriCacheEntry %p '%ls') refcount = %d\n",
pUriCacheEntry, pUriCacheEntry->UriKey.pUri,
pUriCacheEntry->ReferenceCount));
DEREFERENCE_URI_CACHE_ENTRY(pUriCacheEntry, FLUSH);
//
// Perfmon counters
//
UlIncCounter(HttpGlobalCounterTotalFlushedUris);
}
} // UlpFlushUri
/***************************************************************************++
Routine Description:
A filter for UlFlushCacheByProcess. Called by UlpFilteredFlushUriCache.
Arguments:
pUriCacheEntry - the entry to check
pContext - pointer to the UL_APP_POOL_PROCESS that's going away
--***************************************************************************/
UL_CACHE_PREDICATE
UlpFlushFilterProcess(
IN PUL_URI_CACHE_ENTRY pUriCacheEntry,
IN PVOID pContext
)
{
PURI_FILTER_CONTEXT pUriFilterContext = (PURI_FILTER_CONTEXT) pContext;
PUL_APP_POOL_PROCESS pProcess;
//
// Sanity check
//
PAGED_CODE();
ASSERT( IS_VALID_URI_CACHE_ENTRY(pUriCacheEntry) );
ASSERT( IS_VALID_FILTER_CONTEXT(pUriFilterContext)
&& pUriFilterContext->pCallerContext != NULL );
pProcess = (PUL_APP_POOL_PROCESS) pUriFilterContext->pCallerContext;
ASSERT( IS_VALID_AP_PROCESS(pProcess) );
return UlpZombifyEntry(
(BOOLEAN) (pProcess == pUriCacheEntry->pProcess),
FALSE,
pUriCacheEntry,
pUriFilterContext
);
} // UlpFlushFilterProcess
/***************************************************************************++
Routine Description:
A filter for UlpFilteredFlushUriCache. If the given cache entry has a
URI which is prefix of the URI inside the filter context this function
returns delete. Otherwise do not care.
Arguments:
pUriCacheEntry - the entry to check
pContext - pointer to the filter context which holds the appool and
the URI key for the prefix matching.
--***************************************************************************/
UL_CACHE_PREDICATE
UlpFlushFilterUriRecursive(
IN PUL_URI_CACHE_ENTRY pUriCacheEntry,
IN PVOID pContext
)
{
PURI_FILTER_CONTEXT pUriFilterContext = (PURI_FILTER_CONTEXT) pContext;
PUL_APP_POOL_PROCESS pProcess;
BOOLEAN bZombify = FALSE;
UL_CACHE_PREDICATE Predicate = ULC_NO_ACTION;
//
// Sanity check
//
PAGED_CODE();
ASSERT( IS_VALID_URI_CACHE_ENTRY(pUriCacheEntry) );
ASSERT( IS_VALID_FILTER_CONTEXT(pUriFilterContext)
&& pUriFilterContext->pCallerContext != NULL );
pProcess = (PUL_APP_POOL_PROCESS) pUriFilterContext->pCallerContext;
ASSERT( IS_VALID_AP_PROCESS(pProcess) );
if ( pUriFilterContext->UriKey.pUri == NULL
|| pUriFilterContext->UriKey.Length == 0
|| pUriFilterContext->UriKey.Length >
pUriCacheEntry->UriKey.Length
)
{
return ULC_NO_ACTION;
}
bZombify =
(BOOLEAN) (pProcess == pUriCacheEntry->pProcess)
&&
UlPrefixUriKeys(&pUriFilterContext->UriKey,
&pUriCacheEntry->UriKey)
;
Predicate =
UlpZombifyEntry(
bZombify,
FALSE,
pUriCacheEntry,
pUriFilterContext
);
//
// Make sure that the Zombify function do not returns ULC_DELETE_STOP.
// So that our caller proceeds with the search through the entire
// cache table.
//
ASSERT( Predicate == ULC_DELETE || Predicate == ULC_NO_ACTION );
return Predicate;
} // UlpFlushFilterUriRecursive
/***************************************************************************++
Routine Description:
Checks the hash table to make sure there is room for one more
entry of a given size.
Arguments:
EntrySize - the size in bytes of the entry to be added
--***************************************************************************/
BOOLEAN
UlpCheckTableSpace(
IN ULONGLONG EntrySize
)
{
ULONG UriCount;
ULONGLONG ByteCount;
//
// CODEWORK: MaxCacheMegabyteCount of zero should mean adaptive limit,
// but for now I'll take it to mean "no limit".
//
if (g_UriCacheConfig.MaxCacheMegabyteCount == 0)
ByteCount = 0;
else
ByteCount = g_UriCacheStats.ByteCount + ROUND_TO_PAGES(EntrySize);
//
// MaxCacheUriCount of zero means no limit on number of URIs cached
//
if (g_UriCacheConfig.MaxCacheUriCount == 0)
UriCount = 0;
else
UriCount = g_UriCacheStats.UriCount + 1;
if (
UriCount <= g_UriCacheConfig.MaxCacheUriCount &&
ByteCount <= g_UriCacheConfig.MaxCacheByteCount
)
{
return TRUE;
}
else
{
UlTrace(URI_CACHE, (
"Http!UlpCheckTableSpace(%I64u) FALSE\n"
" UriCount = %lu\n"
" ByteCount = %I64u (%luMB)\n"
" MaxCacheUriCount = %lu\n"
" MaxCacheMegabyteCount = %luMB\n"
" MaxCacheByteCount = %I64u\n",
EntrySize,
g_UriCacheStats.UriCount,
g_UriCacheStats.ByteCount,
(ULONG) (g_UriCacheStats.ByteCount >> MEGABYTE_SHIFT),
g_UriCacheConfig.MaxCacheUriCount,
g_UriCacheConfig.MaxCacheMegabyteCount,
g_UriCacheConfig.MaxCacheByteCount
));
return FALSE;
}
} // UlpCheckTableSpace
/***************************************************************************++
Routine Description:
Tries to add a cache entry to the hash table.
Arguments:
pUriCacheEntry - the entry to be added
--***************************************************************************/
NTSTATUS
UlAddCacheEntry(
PUL_URI_CACHE_ENTRY pUriCacheEntry
)
{
NTSTATUS Status;
//
// Sanity check
//
PAGED_CODE();
ASSERT(!g_UriCacheConfig.EnableCache
|| IS_VALID_HASHTABLE(&g_UriCacheTable));
ASSERT( IS_VALID_URI_CACHE_ENTRY(pUriCacheEntry) );
ASSERT(! pUriCacheEntry->Zombie);
pUriCacheEntry->BucketEntry.Next = NULL;
pUriCacheEntry->Cached = FALSE;
// First, check if still has space for storing the cache entry
if (UlpCheckSpaceAndAddEntryStats(pUriCacheEntry))
{
pUriCacheEntry->Cached = TRUE;
//
// Insert this record into the hash table
// Check first to see if the key already presents
//
Status = UlAddToHashTable(&g_UriCacheTable, pUriCacheEntry);
if (!NT_SUCCESS(Status))
{
// This can fail if it's a duplicate name
UlpRemoveEntryStats(pUriCacheEntry);
pUriCacheEntry->Cached = FALSE;
}
}
else
{
Status = STATUS_ALLOTTED_SPACE_EXCEEDED;
}
UlTrace(URI_CACHE, (
"Http!UlAddCacheEntry(urientry %p '%ls') %s added to table. "
"RefCount=%d, lkrc=%d.\n",
pUriCacheEntry, pUriCacheEntry->UriKey.pUri,
pUriCacheEntry->Cached ? "was" : "was not",
pUriCacheEntry->ReferenceCount,
Status
));
return Status;
} // UlAddCacheEntry
/***************************************************************************++
Routine Description:
Check to see if we have space to add this cache entry and if so update
cache statistics to reflect the addition of an entry. This has to be
done together inside a lock.
Arguments:
pUriCacheEntry - entry being added
--***************************************************************************/
BOOLEAN
UlpCheckSpaceAndAddEntryStats(
PUL_URI_CACHE_ENTRY pUriCacheEntry
)
{
KIRQL OldIrql;
ULONG EntrySize;
//
// Sanity check
//
ASSERT( IS_VALID_URI_CACHE_ENTRY(pUriCacheEntry) );
EntrySize = pUriCacheEntry->HeaderLength + pUriCacheEntry->ContentLength;
UlAcquireSpinLock( &g_UriCacheSpinLock, &OldIrql );
if (UlpCheckTableSpace(EntrySize))
{
g_UriCacheStats.UriCount++;
g_UriCacheStats.UriAddedTotal++;
g_UriCacheStats.UriCountMax = MAX(
g_UriCacheStats.UriCountMax,
g_UriCacheStats.UriCount
);
g_UriCacheStats.ByteCount += EntrySize;
g_UriCacheStats.ByteCountMax = MAX(
g_UriCacheStats.ByteCountMax,
g_UriCacheStats.ByteCount
);
UlReleaseSpinLock( &g_UriCacheSpinLock, OldIrql );
//
// Update Uri's site binding info stats.
//
switch (pUriCacheEntry->ConfigInfo.SiteUrlType)
{
case HttpUrlSite_None:
InterlockedIncrement((PLONG) &g_UriCacheStats.UriTypeNotSpecifiedCount);
break;
case HttpUrlSite_Name:
InterlockedIncrement((PLONG) &g_UriCacheStats.UriTypeHostBoundCount);
break;
case HttpUrlSite_NamePlusIP:
InterlockedIncrement((PLONG) &g_UriCacheStats.UriTypeHostPlusIpBoundCount);
break;
case HttpUrlSite_IP:
InterlockedIncrement((PLONG) &g_UriCacheStats.UriTypeIpBoundCount);
break;
case HttpUrlSite_WeakWildcard:
InterlockedIncrement((PLONG) &g_UriCacheStats.UriTypeWildCardCount);
break;
default:
ASSERT(!"Invalid url site binding type while adding to cache !");
break;
}
UlTrace(URI_CACHE, (
"Http!UlpCheckSpaceAndAddEntryStats (urientry %p '%ls')\n",
pUriCacheEntry, pUriCacheEntry->UriKey.pUri
));
//
// Perfmon counters
//
UlIncCounter(HttpGlobalCounterCurrentUrisCached);
UlIncCounter(HttpGlobalCounterTotalUrisCached);
return TRUE;
}
UlReleaseSpinLock( &g_UriCacheSpinLock, OldIrql );
return FALSE;
} // UlpCheckSpaceAndAddEntryStats
/***************************************************************************++
Routine Description:
Updates cache statistics to reflect the removal of an entry
Arguments:
pUriCacheEntry - entry being removed
--***************************************************************************/
VOID
UlpRemoveEntryStats(
PUL_URI_CACHE_ENTRY pUriCacheEntry
)
{
KIRQL OldIrql;
ULONG EntrySize;
//
// Sanity check
//
ASSERT( IS_VALID_URI_CACHE_ENTRY(pUriCacheEntry) );
ASSERT( pUriCacheEntry->Cached );
ASSERT( 0 == pUriCacheEntry->ReferenceCount );
EntrySize = pUriCacheEntry->HeaderLength + pUriCacheEntry->ContentLength;
UlAcquireSpinLock( &g_UriCacheSpinLock, &OldIrql );
g_UriCacheStats.UriCount--;
g_UriCacheStats.ByteCount -= EntrySize;
UlReleaseSpinLock( &g_UriCacheSpinLock, OldIrql );
//
// Update Uri's site binding info stats.
//
switch (pUriCacheEntry->ConfigInfo.SiteUrlType)
{
case HttpUrlSite_None:
InterlockedDecrement((PLONG) &g_UriCacheStats.UriTypeNotSpecifiedCount);
break;
case HttpUrlSite_Name:
InterlockedDecrement((PLONG) &g_UriCacheStats.UriTypeHostBoundCount);
break;
case HttpUrlSite_NamePlusIP:
InterlockedDecrement((PLONG) &g_UriCacheStats.UriTypeHostPlusIpBoundCount);
break;
case HttpUrlSite_IP:
InterlockedDecrement((PLONG) &g_UriCacheStats.UriTypeIpBoundCount);
break;
case HttpUrlSite_WeakWildcard:
InterlockedDecrement((PLONG) &g_UriCacheStats.UriTypeWildCardCount);
break;
default:
ASSERT(!"Invalid url site binding type while adding to cache !");
break;
}
UlTrace(URI_CACHE, (
"Http!UlpRemoveEntryStats (urientry %p '%ls')\n",
pUriCacheEntry, pUriCacheEntry->UriKey.pUri
));
//
// Perfmon counters
//
UlDecCounter(HttpGlobalCounterCurrentUrisCached);
} // UlpRemoveEntryStats
/***************************************************************************++
Routine Description:
Helper function for the filter callbacks indirectly invoked by
UlpFilteredFlushUriCache. Adds deleteable entries to a temporary
list.
Arguments:
MustZombify - if TRUE, add entry to the private zombie list
pUriCacheEntry - entry to zombify
pUriFilterContext - contains private list
--***************************************************************************/
UL_CACHE_PREDICATE
UlpZombifyEntry(
BOOLEAN MustZombify,
BOOLEAN MustResetIndex,
IN PUL_URI_CACHE_ENTRY pUriCacheEntry,
IN PURI_FILTER_CONTEXT pUriFilterContext
)
{
ASSERT( IS_VALID_URI_CACHE_ENTRY(pUriCacheEntry) );
ASSERT(URI_FILTER_CONTEXT_POOL_TAG == pUriFilterContext->Signature);
ASSERT(! pUriCacheEntry->Zombie);
ASSERT(NULL == pUriCacheEntry->ZombieListEntry.Flink);
if (MustZombify)
{
//
// Temporarily bump the refcount up so that it won't go down
// to zero when it's removed from the hash table, automatically
// invoking UlpDestroyUriCacheEntry, which we are trying to defer.
//
pUriCacheEntry->ZombieAddReffed = TRUE;
REFERENCE_URI_CACHE_ENTRY(pUriCacheEntry, ZOMBIFY);
InsertTailList(
&pUriFilterContext->ZombieListHead,
&pUriCacheEntry->ZombieListEntry);
pUriCacheEntry->Zombie = TRUE;
//
// Force Raw Logging code to generate an index record for the
// cache entry, in case there's a send on the fly waiting for
// completion. This is because a flush record will be written
// before the actual record.
//
if (MustResetIndex)
{
InterlockedExchange(
(PLONG) &pUriCacheEntry->BinaryIndexWritten,
0
);
}
//
// reset timer so we can track how long an entry is on the list
//
pUriCacheEntry->ScavengerTicks = 0;
++ pUriFilterContext->ZombieCount;
// now remove it from the hash table
return ULC_DELETE;
}
// do not remove pUriCacheEntry from table
return ULC_NO_ACTION;
} // UlpZombifyEntry
/***************************************************************************++
Routine Description:
Adds a list of entries to the global zombie list, then calls
UlpClearZombieList. This cleans up the list of deferred deletions
built up by UlpFilteredFlushUriCache.
Runs at passive level.
Arguments:
pWorkItem - workitem within a URI_FILTER_CONTEXT containing private list
--***************************************************************************/
VOID
UlpZombifyList(
IN PUL_WORK_ITEM pWorkItem
)
{
PURI_FILTER_CONTEXT pUriFilterContext;
PLIST_ENTRY pContextHead;
PLIST_ENTRY pContextTail;
PLIST_ENTRY pZombieHead;
PAGED_CODE();
ASSERT(NULL != pWorkItem);
pUriFilterContext
= CONTAINING_RECORD(pWorkItem, URI_FILTER_CONTEXT, WorkItem);
ASSERT(URI_FILTER_CONTEXT_POOL_TAG == pUriFilterContext->Signature);
UlTrace(URI_CACHE, (
"http!UlpZombifyList, ctxt = %p\n",
pUriFilterContext
));
UlAcquireResourceExclusive(&g_pUlNonpagedData->UriZombieResource, TRUE);
//
// Splice the entire private list into the head of the Zombie list
//
ASSERT(! IsListEmpty(&pUriFilterContext->ZombieListHead));
pContextHead = pUriFilterContext->ZombieListHead.Flink;
pContextTail = pUriFilterContext->ZombieListHead.Blink;
pZombieHead = g_ZombieListHead.Flink;
pContextTail->Flink = pZombieHead;
pZombieHead->Blink = pContextTail;
g_ZombieListHead.Flink = pContextHead;
pContextHead->Blink = &g_ZombieListHead;
// Update stats
g_UriCacheStats.ZombieCount += pUriFilterContext->ZombieCount;
g_UriCacheStats.ZombieCountMax = MAX(g_UriCacheStats.ZombieCount,
g_UriCacheStats.ZombieCountMax);
#if DBG
{
PLIST_ENTRY pEntry;
ULONG ZombieCount;
// Walk forwards through the zombie list and check that it contains
// exactly as many valid zombied UriCacheEntries as we expect.
for (pEntry = g_ZombieListHead.Flink, ZombieCount = 0;
pEntry != &g_ZombieListHead;
pEntry = pEntry->Flink, ++ZombieCount)
{
PUL_URI_CACHE_ENTRY pUriCacheEntry
= CONTAINING_RECORD(pEntry, UL_URI_CACHE_ENTRY, ZombieListEntry);
ASSERT( IS_VALID_URI_CACHE_ENTRY(pUriCacheEntry) );
ASSERT(pUriCacheEntry->Zombie);
ASSERT(pUriCacheEntry->ZombieAddReffed
? pUriCacheEntry->ScavengerTicks == 0
: pUriCacheEntry->ScavengerTicks > 0);
ASSERT(ZombieCount < g_UriCacheStats.ZombieCount);
}
ASSERT(ZombieCount == g_UriCacheStats.ZombieCount);
// And backwards too, like Ginger Rogers
for (pEntry = g_ZombieListHead.Blink, ZombieCount = 0;
pEntry != &g_ZombieListHead;
pEntry = pEntry->Blink, ++ZombieCount)
{
PUL_URI_CACHE_ENTRY pUriCacheEntry
= CONTAINING_RECORD(pEntry, UL_URI_CACHE_ENTRY, ZombieListEntry);
ASSERT( IS_VALID_URI_CACHE_ENTRY(pUriCacheEntry) );
ASSERT(pUriCacheEntry->Zombie);
ASSERT(pUriCacheEntry->ZombieAddReffed
? pUriCacheEntry->ScavengerTicks == 0
: pUriCacheEntry->ScavengerTicks > 0);
ASSERT(ZombieCount < g_UriCacheStats.ZombieCount);
}
ASSERT(ZombieCount == g_UriCacheStats.ZombieCount);
}
#endif // DBG
UlReleaseResource(&g_pUlNonpagedData->UriZombieResource);
UL_FREE_POOL_WITH_SIG(pUriFilterContext, URI_FILTER_CONTEXT_POOL_TAG);
// Now purge those entries, if there are no outstanding references
UlpClearZombieList();
} // UlpZombifyList
/***************************************************************************++
Routine Description:
Removes entries based on a caller specified filter. The caller
provides a boolean function which takes a cache entry as a
parameter. The function will be called with each item in the cache.
The function should conclude with a call to UlpZombifyEntry, passing
in whether or not the item should be deleted. See sample usage
elsewhere in this file. The deletion of the entries is deferred
Arguments:
pFilterRoutine - A pointer to the filter function
pCallerContext - a parameter to the filter function
--***************************************************************************/
VOID
UlpFilteredFlushUriCache(
IN PUL_URI_FILTER pFilterRoutine,
IN PVOID pCallerContext,
IN PWSTR pUri,
IN ULONG Length
)
{
UlpFilteredFlushUriCacheWorker( pFilterRoutine,
pCallerContext,
pUri,
Length,
FALSE );
} // UlpFilteredFlushUriCache
/***************************************************************************++
Routine Description:
Removes entries based on a caller specified filter. The caller
provides a boolean function which takes a cache entry as a
parameter. The function will be called with each item in the cache.
The function should conclude with a call to UlpZombifyEntry, passing
in whether or not the item should be deleted. See sample usage
elsewhere in this file. The deletion of the entries is completed inline
Arguments:
pFilterRoutine - A pointer to the filter function
pCallerContext - a parameter to the filter function
--***************************************************************************/
VOID
UlpFilteredFlushUriCacheInline(
IN PUL_URI_FILTER pFilterRoutine,
IN PVOID pCallerContext,
IN PWSTR pUri,
IN ULONG Length
)
{
UlpFilteredFlushUriCacheWorker( pFilterRoutine,
pCallerContext,
pUri,
Length,
TRUE );
} // UlpFilteredFlushUriCacheInline
/***************************************************************************++
Routine Description:
Removes entries based on a caller specified filter. The caller
provides a boolean function which takes a cache entry as a
parameter. The function will be called with each item in the cache.
The function should conclude with a call to UlpZombifyEntry, passing
in whether or not the item should be deleted. See sample usage
elsewhere in this file.
Arguments:
pFilterRoutine - A pointer to the filter function
pCallerContext - a parameter to the filter function
InlineFlush - If FALSE, queue a work item to delete entries,
If TRUE, delete them now
--***************************************************************************/
VOID
UlpFilteredFlushUriCacheWorker(
IN PUL_URI_FILTER pFilterRoutine,
IN PVOID pCallerContext,
IN PWSTR pUri,
IN ULONG Length,
IN BOOLEAN InlineFlush
)
{
PURI_FILTER_CONTEXT pUriFilterContext;
ULONG ZombieCount = 0;
//
// sanity check
//
PAGED_CODE();
ASSERT( NULL != pFilterRoutine );
//
// Perfmon counters
//
UlIncCounter(HttpGlobalCounterUriCacheFlushes);
//
// Short-circuit if the hashtable is empty. Traversing the entire
// hashtable is expensive.
//
if (0 == g_UriCacheStats.UriCount)
{
UlTrace(URI_CACHE,
("Http!UlpFilteredFlushUriCache(filt=%p, caller ctxt=%p): "
"Not flushing because UriCount==0.\n",
pFilterRoutine, pCallerContext
));
return;
}
pUriFilterContext = UL_ALLOCATE_STRUCT(
NonPagedPool,
URI_FILTER_CONTEXT,
URI_FILTER_CONTEXT_POOL_TAG);
if (pUriFilterContext == NULL)
return;
UlInitializeWorkItem(&pUriFilterContext->WorkItem);
pUriFilterContext->Signature = URI_FILTER_CONTEXT_POOL_TAG;
pUriFilterContext->ZombieCount = 0;
InitializeListHead(&pUriFilterContext->ZombieListHead);
pUriFilterContext->pCallerContext = pCallerContext;
KeQuerySystemTime(&pUriFilterContext->Now);
//
// Store the Uri Info for the recursive Uri Flushes.
//
if (pUri && Length)
{
pUriFilterContext->UriKey.Hash = 0;
pUriFilterContext->UriKey.Length = Length;
pUriFilterContext->UriKey.pUri = pUri;
pUriFilterContext->UriKey.pPath = NULL;
}
else
{
pUriFilterContext->UriKey.Hash = 0;
pUriFilterContext->UriKey.Length = 0;
pUriFilterContext->UriKey.pUri = NULL;
pUriFilterContext->UriKey.pPath = NULL;
}
UlTrace(URI_CACHE, (
"Http!UlpFilteredFlushUriCache("
"filt=%p, filter ctxt=%p, caller ctxt=%p)\n",
pFilterRoutine, pUriFilterContext, pCallerContext
));
if (IS_VALID_HASHTABLE(&g_UriCacheTable))
{
ZombieCount = UlFilterFlushHashTable(
&g_UriCacheTable,
pFilterRoutine,
pUriFilterContext
);
ASSERT(ZombieCount == pUriFilterContext->ZombieCount);
if (0 != ZombieCount)
{
UlAddCounter(HttpGlobalCounterTotalFlushedUris, ZombieCount);
if( InlineFlush ) {
UL_CALL_PASSIVE(
&pUriFilterContext->WorkItem,
UlpZombifyList
);
} else {
UL_QUEUE_WORK_ITEM(
&pUriFilterContext->WorkItem,
UlpZombifyList
);
}
}
else
{
UL_FREE_POOL_WITH_SIG(pUriFilterContext,
URI_FILTER_CONTEXT_POOL_TAG);
}
UlTrace(URI_CACHE, (
"Http!UlpFilteredFlushUriCache(filt=%p, caller ctxt=%p)"
" Zombified: %d\n",
pFilterRoutine,
pCallerContext,
ZombieCount
));
}
} // UlpFilteredFlushUriCacheWorker
/***************************************************************************++
Routine Description:
Scans the zombie list for entries whose refcount has dropped to "zero".
(The calling routine is generally expected to have added a reference
(and set the ZombieAddReffed field within the entries), so that
otherwise unreferenced entries will actually have a refcount of one. It
works this way because we don't want the scavenger directly triggering
calls to UlpDestroyUriCacheEntry)
--***************************************************************************/
VOID
UlpClearZombieList(
VOID
)
{
ULONG ZombiesFreed = 0;
ULONG ZombiesSpared = 0;
PLIST_ENTRY pCurrent;
//
// sanity check
//
PAGED_CODE();
UlAcquireResourceExclusive(&g_pUlNonpagedData->UriZombieResource, TRUE);
pCurrent = g_ZombieListHead.Flink;
while (pCurrent != &g_ZombieListHead)
{
PUL_URI_CACHE_ENTRY pUriCacheEntry
= CONTAINING_RECORD(pCurrent, UL_URI_CACHE_ENTRY, ZombieListEntry);
ASSERT( IS_VALID_URI_CACHE_ENTRY(pUriCacheEntry) );
ASSERT(pUriCacheEntry->Zombie);
//
// get next entry now, because we might destroy this one
//
pCurrent = pCurrent->Flink;
//
// ReferenceCount is modified with interlocked ops, but in
// this case we know the ReferenceCount can't go up on
// a Zombie, and if an entry hits one just after we look
// at it, we'll just get it on the next pass
//
if (pUriCacheEntry->ZombieAddReffed)
{
BOOLEAN LastRef = (BOOLEAN) (pUriCacheEntry->ReferenceCount == 1);
if (LastRef)
{
RemoveEntryList(&pUriCacheEntry->ZombieListEntry);
pUriCacheEntry->ZombieListEntry.Flink = NULL;
++ ZombiesFreed;
ASSERT(g_UriCacheStats.ZombieCount > 0);
-- g_UriCacheStats.ZombieCount;
}
else
{
// track age of zombie
++ pUriCacheEntry->ScavengerTicks;
++ ZombiesSpared;
}
pUriCacheEntry->ZombieAddReffed = FALSE;
DEREFERENCE_URI_CACHE_ENTRY(pUriCacheEntry, UNZOMBIFY);
// NOTE: ref can go to zero, so ZombiesSpared may be wrong.
}
else
{
ASSERT(pUriCacheEntry->ScavengerTicks > 0);
// track age of zombie
++ pUriCacheEntry->ScavengerTicks;
++ ZombiesSpared;
if (pUriCacheEntry->ScavengerTicks > ZOMBIE_AGE_THRESHOLD)
{
UlTrace(URI_CACHE, (
"Http!UlpClearZombieList()\n"
" WARNING: %p '%ls' (refs = %d) "
"has been a zombie for %d ticks!\n",
pUriCacheEntry, pUriCacheEntry->UriKey.pUri,
pUriCacheEntry->ReferenceCount,
pUriCacheEntry->ScavengerTicks
));
}
}
}
ASSERT((g_UriCacheStats.ZombieCount == 0)
== IsListEmpty(&g_ZombieListHead));
UlReleaseResource(&g_pUlNonpagedData->UriZombieResource);
UlTrace(URI_CACHE,
("Http!UlpClearZombieList(): Freed = %d, Remaining = %d.\n\n",
ZombiesFreed,
ZombiesSpared
));
} // UlpClearZombieList
/***************************************************************************++
Routine Description:
Frees a URI entry to the pool. Removes references to other objects.
Arguments:
pTracker - Supplies the UL_READ_TRACKER to manipulate.
--***************************************************************************/
VOID
UlpDestroyUriCacheEntry(
PUL_URI_CACHE_ENTRY pUriCacheEntry
)
{
NTSTATUS Status;
//
// Sanity check
//
PAGED_CODE();
ASSERT( IS_VALID_URI_CACHE_ENTRY(pUriCacheEntry) );
// CODEWORK: real cleanup will need to release
// config & process references.
ASSERT(0 == pUriCacheEntry->ReferenceCount);
UlTrace(URI_CACHE,
("Http!UlpDestroyUriCacheEntry: Entry %p, '%ls', Refs=%d\n",
pUriCacheEntry, pUriCacheEntry->UriKey.pUri,
pUriCacheEntry->ReferenceCount
));
//
// Release the UL_URL_CONFIG_GROUP_INFO block
//
if (IS_RESPONSE_CACHE_ENTRY(pUriCacheEntry))
{
Status = UlConfigGroupInfoRelease(&pUriCacheEntry->ConfigInfo);
ASSERT(NT_SUCCESS(Status));
}
else
{
ASSERT(IS_FRAGMENT_CACHE_ENTRY(pUriCacheEntry));
ASSERT(!IS_VALID_URL_CONFIG_GROUP_INFO(&pUriCacheEntry->ConfigInfo));
}
//
// Remove from g_ZombieListHead if neccessary
//
if (pUriCacheEntry->ZombieListEntry.Flink != NULL)
{
ASSERT(pUriCacheEntry->Zombie);
ASSERT(! pUriCacheEntry->ZombieAddReffed);
UlAcquireResourceExclusive(
&g_pUlNonpagedData->UriZombieResource,
TRUE);
if (pUriCacheEntry->ZombieListEntry.Flink != NULL)
{
RemoveEntryList(&pUriCacheEntry->ZombieListEntry);
ASSERT(g_UriCacheStats.ZombieCount > 0);
-- g_UriCacheStats.ZombieCount;
}
UlReleaseResource(&g_pUlNonpagedData->UriZombieResource);
}
UlFreeCacheEntry( pUriCacheEntry );
} // UlpDestroyUriCacheEntry
/***************************************************************************++
Routine Description:
Looks through the cache for expired entries to put on the zombie list,
and then empties out the list. Increments ScavengerTicks of each entry
Arguments:
Age - #Scavenger calls since last periodic cleanup
--***************************************************************************/
VOID
UlPeriodicCacheScavenger(
ULONG Age
)
{
PAGED_CODE();
UlpFilteredFlushUriCacheInline(UlpFlushFilterPeriodicScavenger,
&Age, NULL, 0);
} // UlPeriodicScavenger
/***************************************************************************++
Routine Description:
A filter for UlPeriodicCacheScavenger. Called by UlpFilteredFlushUriCache.
Increments pUriCacheEntry->ScavengerTicks
Arguments:
pUriCacheEntry - the entry to check
pContext - Has pointer to the max age in the pCallerContext field
--***************************************************************************/
UL_CACHE_PREDICATE
UlpFlushFilterPeriodicScavenger(
IN PUL_URI_CACHE_ENTRY pUriCacheEntry,
IN PVOID pContext
)
{
PURI_FILTER_CONTEXT pUriFilterContext = (PURI_FILTER_CONTEXT) pContext;
BOOLEAN ToZombify;
ULONG Age;
//
// Sanity check
//
PAGED_CODE();
ASSERT( IS_VALID_URI_CACHE_ENTRY(pUriCacheEntry) );
ASSERT( pUriFilterContext != NULL
&& URI_FILTER_CONTEXT_POOL_TAG == pUriFilterContext->Signature
&& pUriFilterContext->pCallerContext != NULL );
Age = *((PULONG)pUriFilterContext->pCallerContext);
ToZombify = (BOOLEAN) (pUriCacheEntry->ScavengerTicks > Age);
pUriCacheEntry->ScavengerTicks = 1; // reset to 0 on cache hit
//
// Check for expiration time as well
//
if (!ToZombify &&
HttpCachePolicyTimeToLive == pUriCacheEntry->CachePolicy.Policy )
{
ASSERT( 0 != pUriCacheEntry->ExpirationTime.QuadPart );
ToZombify =
(BOOLEAN) (pUriFilterContext->Now.QuadPart >
pUriCacheEntry->ExpirationTime.QuadPart);
}
return UlpZombifyEntry(
ToZombify,
FALSE,
pUriCacheEntry,
pUriFilterContext
);
} // UlpFlushFilterPeriodicScavenger
/***************************************************************************++
Routine Description:
Purges entries from the cache until a specified amount of memory is
reclaimed
Arguments:
Blocks - Number of 8-byte blocks to Reclaim
Age - # Scavenger calls since past periodic cleanup
--***************************************************************************/
VOID
UlTrimCache(
IN ULONG_PTR Pages,
IN ULONG Age
)
{
LONG_PTR PagesTarget;
UL_CACHE_TRIM_FILTER_CONTEXT FilterContext;
ASSERT((LONG)Pages > 0);
ASSERT((LONG)Age > 0);
PagesTarget = UlGetHashTablePages() - Pages;
if(PagesTarget < 0) {
PagesTarget = 0;
}
FilterContext.Pages = Pages;
FilterContext.Age = Age;
while((FilterContext.Pages > 0) && (FilterContext.Age >= 0)
&& ((ULONG)PagesTarget < UlGetHashTablePages())) {
UlTraceVerbose(URI_CACHE, ("UlTrimCache: Age %d Target %d\n", FilterContext.Age, FilterContext.Pages));
UlpFilteredFlushUriCacheInline( UlpFlushFilterTrimCache, &FilterContext, NULL, 0 );
FilterContext.Age--;
}
UlTraceVerbose(URI_CACHE, ("UlTrimCache: Finished: Age %d Pages %d\n", FilterContext.Age, FilterContext.Pages));
UlpFilteredFlushUriCacheInline( UlpFlushFilterIncScavengerTicks, NULL, NULL, 0 );
} // UlTrimCache
/***************************************************************************++
Routine Description:
A filter for UlTrimCache. Called by UlpFilteredFlushUriCache.
Arguments:
pUriCacheEntry - the entry to check
pContext - Has a pointer to pCallerContext
pCallerContext[0] = Blocks to trim
pCallerContext[1] = Current Age
--***************************************************************************/
UL_CACHE_PREDICATE
UlpFlushFilterTrimCache(
IN PUL_URI_CACHE_ENTRY pUriCacheEntry,
IN PVOID pContext
)
{
PURI_FILTER_CONTEXT pUriFilterContext = (PURI_FILTER_CONTEXT) pContext;
ULONG MinimumAge;
ULONG_PTR PagesReclaimed;
PUL_CACHE_TRIM_FILTER_CONTEXT FilterContext;
UL_CACHE_PREDICATE ToZombify;
// Sanity check
PAGED_CODE();
ASSERT( IS_VALID_URI_CACHE_ENTRY(pUriCacheEntry) );
ASSERT( pUriFilterContext != NULL
&& URI_FILTER_CONTEXT_POOL_TAG == pUriFilterContext->Signature
&& pUriFilterContext->pCallerContext != NULL );
FilterContext = (PUL_CACHE_TRIM_FILTER_CONTEXT) pUriFilterContext->pCallerContext;
if(FilterContext->Pages <= 0) {
return ULC_ABORT;
}
ASSERT( FilterContext->Pages > 0 );
ASSERT( (LONG)FilterContext->Age >= 0 );
MinimumAge = FilterContext->Age;
ToZombify = UlpZombifyEntry(
(BOOLEAN) (pUriCacheEntry->ScavengerTicks >= MinimumAge),
FALSE,
pUriCacheEntry,
pUriFilterContext
);
if(ToZombify == ULC_DELETE) {
PagesReclaimed = pUriCacheEntry->NumPages;
FilterContext->Pages -= PagesReclaimed;
}
return ToZombify;
} // UlpFlushFilterTrimCache
/***************************************************************************++
Routine Description:
Increment Scavenger Ticks
Arguments:
pUriCacheEntry - the entry to check
pContext - ignored
--***************************************************************************/
UL_CACHE_PREDICATE
UlpFlushFilterIncScavengerTicks(
IN PUL_URI_CACHE_ENTRY pUriCacheEntry,
IN PVOID pContext
)
{
PURI_FILTER_CONTEXT pUriFilterContext = (PURI_FILTER_CONTEXT) pContext;
PAGED_CODE();
ASSERT( IS_VALID_URI_CACHE_ENTRY(pUriCacheEntry) );
ASSERT( pUriFilterContext != NULL
&& URI_FILTER_CONTEXT_POOL_TAG == pUriFilterContext->Signature
&& pUriFilterContext->pCallerContext == NULL );
pUriCacheEntry->ScavengerTicks++;
return UlpZombifyEntry(
FALSE,
FALSE,
pUriCacheEntry,
pUriFilterContext
);
} // UlpFlushFilterIncScavengerTicks
/***************************************************************************++
Routine Description:
Looks through the cache for centralized logged entries. Mark them all
NOT logged.
This funtion is normally called when a binary log file is recycled
or reconfigured.
Arguments:
pContext - When we enable multiple binary logs, we must only mess with
those cache entries logged to this specific binary log file
until then discarted.
--***************************************************************************/
VOID
UlClearCentralizedLogged(
IN PVOID pContext
)
{
PAGED_CODE();
UNREFERENCED_PARAMETER(pContext);
ASSERT(!g_UriCacheConfig.EnableCache
|| IS_VALID_HASHTABLE(&g_UriCacheTable));
if (g_UriCacheConfig.EnableCache)
{
UlTrace2Either(BINARY_LOGGING, URI_CACHE,(
"Http!UlClearCentralizedLogged()\n"));
UlpFilteredFlushUriCache(
UlpFlushFilterClearCentralizedLogged,
NULL,
NULL,
0
);
}
} // UlClearCentralizedLogged
/***************************************************************************++
Routine Description:
Basically a fake filter, which will always returns FALSE. But it updates
the CentralizedLogged flag on the entry.
Arguments:
pUriCacheEntry - the entry to check
pContext - ignored
--***************************************************************************/
UL_CACHE_PREDICATE
UlpFlushFilterClearCentralizedLogged(
IN PUL_URI_CACHE_ENTRY pUriCacheEntry,
IN PVOID pContext
)
{
#if DBG
PURI_FILTER_CONTEXT pUriFilterContext = (PURI_FILTER_CONTEXT) pContext;
#else
UNREFERENCED_PARAMETER(pContext);
#endif
//
// Sanity check
//
PAGED_CODE();
ASSERT( IS_VALID_URI_CACHE_ENTRY(pUriCacheEntry) );
ASSERT( pUriFilterContext != NULL
&& URI_FILTER_CONTEXT_POOL_TAG == pUriFilterContext->Signature
&& pUriFilterContext->pCallerContext == NULL );
InterlockedExchange((PLONG) &pUriCacheEntry->BinaryIndexWritten, 0);
//
// Updated the flag. Just bail out.
//
return ULC_NO_ACTION;
} // UlpFlushFilterClearCentralizedLogged
/***************************************************************************++
Routine Description:
Determine if the Translate header is present AND has a value of 'F' or 'f'.
Arguments:
pRequest - Supplies the request to query.
Return Value:
BOOLEAN - TRUE if "Translate: F", FALSE otherwise
--***************************************************************************/
BOOLEAN
UlpQueryTranslateHeader(
IN PUL_INTERNAL_REQUEST pRequest
)
{
BOOLEAN ret = FALSE;
if ( pRequest->HeaderValid[HttpHeaderTranslate] )
{
PUCHAR pValue = pRequest->Headers[HttpHeaderTranslate].pHeader;
ASSERT(NULL != pValue);
if (('f' == pValue[0] || 'F' == pValue[0]) && '\0' == pValue[1])
{
ASSERT(pRequest->Headers[HttpHeaderTranslate].HeaderLength == 1);
ret = TRUE;
}
}
return ret;
} // UlpQueryTranslateHeader
/***************************************************************************++
Routine Description:
Determine if the Expect header is present AND has a value
of EXACTLY "100-continue".
Arguments:
pRequest - Supplies the request to check.
Return Value:
BOOLEAN - TRUE if "Expect: 100-continue" or not present, FALSE otherwise
--***************************************************************************/
BOOLEAN
UlpQueryExpectHeader(
IN PUL_INTERNAL_REQUEST pRequest
)
{
BOOLEAN ret = TRUE;
if ( pRequest->HeaderValid[HttpHeaderExpect] )
{
PCSTR pValue = (PCSTR) pRequest->Headers[HttpHeaderExpect].pHeader;
ASSERT(NULL != pValue);
if ((strlen(pValue) != HTTP_CONTINUE_LENGTH) ||
(0 != strncmp(pValue, HTTP_100_CONTINUE, HTTP_CONTINUE_LENGTH)))
{
ret = FALSE;
}
}
return ret;
} // UlQueryExpectHeader
/***************************************************************************++
Routine Description:
Add a reference on a cache entry
Arguments:
pUriCacheEntry - the entry to addref
--***************************************************************************/
LONG
UlAddRefUriCacheEntry(
IN PUL_URI_CACHE_ENTRY pUriCacheEntry,
IN REFTRACE_ACTION Action
REFERENCE_DEBUG_FORMAL_PARAMS
)
{
LONG RefCount;
UNREFERENCED_PARAMETER(Action);
ASSERT( IS_VALID_URI_CACHE_ENTRY(pUriCacheEntry) );
RefCount = InterlockedIncrement(&pUriCacheEntry->ReferenceCount);
WRITE_REF_TRACE_LOG(
g_pUriTraceLog,
Action,
RefCount,
pUriCacheEntry,
pFileName,
LineNumber
);
UlTrace(URI_CACHE, (
"Http!UlAddRefUriCacheEntry: (%p, '%ls', refcount=%d)\n",
pUriCacheEntry, pUriCacheEntry->UriKey.pUri, RefCount
));
ASSERT(RefCount > 0);
return RefCount;
} // UlAddRefUriCacheEntry
/***************************************************************************++
Routine Description:
Release a reference on a cache entry
Arguments:
pUriCacheEntry - the entry to release
--***************************************************************************/
LONG
UlReleaseUriCacheEntry(
IN PUL_URI_CACHE_ENTRY pUriCacheEntry,
IN REFTRACE_ACTION Action
REFERENCE_DEBUG_FORMAL_PARAMS
)
{
LONG RefCount;
UNREFERENCED_PARAMETER(Action);
ASSERT( IS_VALID_URI_CACHE_ENTRY(pUriCacheEntry) );
RefCount = InterlockedDecrement(&pUriCacheEntry->ReferenceCount);
WRITE_REF_TRACE_LOG(
g_pUriTraceLog,
Action,
RefCount,
pUriCacheEntry,
pFileName,
LineNumber
);
UlTrace(URI_CACHE, (
"Http!UlReleaseUriCacheEntry: (%p, '%ls', refcount=%d)\n",
pUriCacheEntry, pUriCacheEntry->UriKey.pUri, RefCount
));
ASSERT(RefCount >= 0);
if (RefCount == 0)
{
if (pUriCacheEntry->Cached)
UlpRemoveEntryStats(pUriCacheEntry);
UlpDestroyUriCacheEntry(pUriCacheEntry);
}
return RefCount;
} // UlReleaseUriCacheEntry
/***************************************************************************++
Routine Description:
UL_URI_CACHE_ENTRY pseudo-constructor. Primarily used for
AddRef and tracelogging.
Arguments:
pUriCacheEntry - the entry to initialize
Hash - Hash code of pUrl
Length - Length (in bytes) of pUrl
pUrl - Unicode URL to copy
pAbsPath - Points to the AbsPath of the Url.
pRoutingToken - Optional
RoutingTokenLength - Optional (in bytes)
--***************************************************************************/
VOID
UlInitCacheEntry(
PUL_URI_CACHE_ENTRY pUriCacheEntry,
ULONG Hash,
ULONG Length,
PCWSTR pUrl,
PCWSTR pAbsPath,
PCWSTR pRoutingToken,
USHORT RoutingTokenLength
)
{
pUriCacheEntry->Signature = UL_URI_CACHE_ENTRY_POOL_TAG;
pUriCacheEntry->ReferenceCount = 0;
pUriCacheEntry->HitCount = 1;
pUriCacheEntry->Zombie = FALSE;
pUriCacheEntry->ZombieAddReffed = FALSE;
pUriCacheEntry->ZombieListEntry.Flink = NULL;
pUriCacheEntry->ZombieListEntry.Blink = NULL;
pUriCacheEntry->Cached = FALSE;
pUriCacheEntry->ScavengerTicks = 0;
pUriCacheEntry->UriKey.Hash = Hash;
pUriCacheEntry->UriKey.Length = Length;
pUriCacheEntry->UriKey.pUri = (PWSTR) ((PCHAR)pUriCacheEntry +
ALIGN_UP(sizeof(UL_URI_CACHE_ENTRY), PVOID));
if (pRoutingToken)
{
PWSTR pUri = pUriCacheEntry->UriKey.pUri;
ASSERT(wcslen(pRoutingToken) * sizeof(WCHAR) == RoutingTokenLength);
pUriCacheEntry->UriKey.Length += RoutingTokenLength;
RtlCopyMemory(
pUri,
pRoutingToken,
RoutingTokenLength
);
RtlCopyMemory(
&pUri[RoutingTokenLength/sizeof(WCHAR)],
pUrl,
Length + sizeof(WCHAR)
);
ASSERT(wcslen(pUri) * sizeof(WCHAR) == pUriCacheEntry->UriKey.Length );
pUriCacheEntry->UriKey.pPath =
pUri + (RoutingTokenLength / sizeof(WCHAR));
UlTrace(URI_CACHE, (
"Http!UlInitCacheEntry Extended (%p = '%ls' + '%ls')\n",
pUriCacheEntry, pRoutingToken, pUrl
));
}
else
{
RtlCopyMemory(
pUriCacheEntry->UriKey.pUri,
pUrl,
pUriCacheEntry->UriKey.Length + sizeof(WCHAR)
);
if (pAbsPath)
{
ASSERT( pAbsPath >= pUrl );
ASSERT( DIFF(pAbsPath - pUrl) <= Length );
pUriCacheEntry->UriKey.pPath =
pUriCacheEntry->UriKey.pUri + DIFF(pAbsPath - pUrl);
}
else
{
// Possibly a fragment cache entry.
pUriCacheEntry->UriKey.pPath = NULL;
}
UlTrace(URI_CACHE, (
"Http!UlInitCacheEntry (%p = '%ls')\n",
pUriCacheEntry, pUriCacheEntry->UriKey.pUri
));
}
REFERENCE_URI_CACHE_ENTRY(pUriCacheEntry, CREATE);
} // UlInitCacheEntry
/***************************************************************************++
Routine Description:
Adds a fragment cache entry to the response cache database.
Arguments:
pProcess - process that is adding the fragment cache entry
pFragmentName - key of the fragment cache entry
pDataChunk - specifies the data chunk to be put into the cache entry
pCachePolicy - specifies the policy of the new fragment cache entry
Return Value:
NTSTATUS
--***************************************************************************/
NTSTATUS
UlAddFragmentToCache(
IN PUL_APP_POOL_PROCESS pProcess,
IN PUNICODE_STRING pFragmentName,
IN PHTTP_DATA_CHUNK pDataChunk,
IN PHTTP_CACHE_POLICY pCachePolicy,
IN KPROCESSOR_MODE RequestorMode
)
{
PUL_APP_POOL_OBJECT pAppPool;
PWSTR pAppPoolName;
PWSTR pEndName;
ULONG AppPoolNameLength;
PUL_URI_CACHE_ENTRY pCacheEntry;
ULONGLONG Length;
PFAST_IO_DISPATCH pFastIoDispatch;
PFILE_OBJECT pFileObject;
PDEVICE_OBJECT pDeviceObject;
FILE_STANDARD_INFORMATION FileInfo;
IO_STATUS_BLOCK IoStatusBlock;
PUCHAR pReadBuffer;
PLARGE_INTEGER pOffset;
HTTP_DATA_CHUNK LocalDataChunk;
NTSTATUS Status;
UNICODE_STRING FragmentName;
UL_URL_CONFIG_GROUP_INFO UrlInfo;
PWSTR pSanitizedUrl;
HTTP_PARSED_URL ParsedUrl;
HTTP_BYTE_RANGE ByteRange = {0,0};
//
// Validate if the data chunk can be put into the cache.
//
if (FALSE == g_UriCacheConfig.EnableCache)
{
return STATUS_NOT_SUPPORTED;
}
//
// Use a local copy of DataChunk onwards to ensure fields inside won't
// get changed.
//
LocalDataChunk = *pDataChunk;
if (HttpDataChunkFromMemory != LocalDataChunk.DataChunkType &&
HttpDataChunkFromFileHandle != LocalDataChunk.DataChunkType)
{
return STATUS_NOT_IMPLEMENTED;
}
pCacheEntry = NULL;
pFileObject = NULL;
pReadBuffer = NULL;
pSanitizedUrl = NULL;
UlInitializeUrlInfo(&UrlInfo);
//
// Validate the AppPool name of the process matches the first portion
// of the fragment name before "/".
//
__try
{
Status =
UlProbeAndCaptureUnicodeString(
pFragmentName,
RequestorMode,
&FragmentName,
0
);
if (!NT_SUCCESS(Status))
{
goto end;
}
//
// The fragment name convention is different for a transient app
// (where AppPool name is "") and a normal WAS-type of app. The
// former starts with a URL that the AppPool listens, the latter
// starts using the AppPool name itself. The name validation has
// to be done differently as well.
//
//
pAppPool = pProcess->pAppPool;
if (pAppPool->NameLength)
{
pAppPoolName = FragmentName.Buffer;
pEndName = wcschr(pAppPoolName, L'/');
if (!pEndName)
{
Status = STATUS_INVALID_PARAMETER;
goto end;
}
AppPoolNameLength = DIFF((PUCHAR)pEndName - (PUCHAR)pAppPoolName);
if (pAppPool->NameLength != AppPoolNameLength ||
_wcsnicmp(
pAppPool->pName,
pAppPoolName,
AppPoolNameLength / sizeof(WCHAR)
))
{
Status = STATUS_INVALID_PARAMETER;
goto end;
}
}
else
{
//
// Do a reverse-lookup to find out the AppPool that listens on the
// URL/FragmentName passed in. Need to sanitize the URL because
// of IP based URLs get internally expanded when stored in CG.
// For instance, when stored, http://127.0.0.1:80/Test/ becomes
// http://127.0.0.1:80:127.0.0.1/Test/.
//
Status = UlSanitizeUrl(
FragmentName.Buffer,
FragmentName.Length / sizeof(WCHAR),
FALSE,
&pSanitizedUrl,
&ParsedUrl
);
if (!NT_SUCCESS(Status))
{
goto end;
}
ASSERT(pSanitizedUrl);
Status = UlGetConfigGroupInfoForUrl(
pSanitizedUrl,
NULL,
&UrlInfo
);
if (!NT_SUCCESS(Status))
{
goto end;
}
if (UrlInfo.pAppPool != pAppPool)
{
Status = STATUS_INVALID_ID_AUTHORITY;
goto end;
}
}
if (HttpDataChunkFromMemory == LocalDataChunk.DataChunkType)
{
//
// Cache a FromMemory data chunk. ContentLength is BufferLength.
//
Length = LocalDataChunk.FromMemory.BufferLength;
}
else
{
//
// Cache a FromFileHandle data chunk. ContentLength is the size
// of the file.
//
Status = ObReferenceObjectByHandle(
LocalDataChunk.FromFileHandle.FileHandle,
FILE_READ_ACCESS,
*IoFileObjectType,
UserMode,
(PVOID *) &pFileObject,
NULL
);
if (!NT_SUCCESS(Status))
{
goto end;
}
//
// Non-cached reads are not supported since they require
// us to align both file offset and length.
//
if (!(pFileObject->Flags & FO_CACHE_SUPPORTED))
{
Status = STATUS_NOT_SUPPORTED;
goto end;
}
pDeviceObject = IoGetRelatedDeviceObject(pFileObject);
pFastIoDispatch = pDeviceObject->DriverObject->FastIoDispatch;
if (!pFastIoDispatch ||
pFastIoDispatch->SizeOfFastIoDispatch <=
FIELD_OFFSET(FAST_IO_DISPATCH, FastIoQueryStandardInfo) ||
!pFastIoDispatch->FastIoQueryStandardInfo ||
!pFastIoDispatch->FastIoQueryStandardInfo(
pFileObject,
TRUE,
&FileInfo,
&IoStatusBlock,
pDeviceObject
))
{
Status = ZwQueryInformationFile(
LocalDataChunk.FromFileHandle.FileHandle,
&IoStatusBlock,
&FileInfo,
sizeof(FILE_STANDARD_INFORMATION),
FileStandardInformation
);
if (!NT_SUCCESS(Status))
{
goto end;
}
}
Status = UlSanitizeFileByteRange(
&LocalDataChunk.FromFileHandle.ByteRange,
&ByteRange,
FileInfo.EndOfFile.QuadPart
);
if (!NT_SUCCESS(Status))
{
goto end;
}
Length = ByteRange.Length.QuadPart;
}
//
// It doesn't make sense to add a zero-length fragment.
//
if (!Length)
{
Status = STATUS_INVALID_PARAMETER;
goto end;
}
//
// Enforce the MaxUriBytes limit.
//
if (Length > g_UriCacheConfig.MaxUriBytes)
{
Status = STATUS_NOT_SUPPORTED;
goto end;
}
//
// Build a fragment cache entry.
//
Status = UlpCreateFragmentCacheEntry(
pProcess,
FragmentName.Buffer,
FragmentName.Length,
(ULONG) Length,
pCachePolicy,
&pCacheEntry
);
if (!NT_SUCCESS(Status))
{
goto end;
}
ASSERT(pCacheEntry);
//
// Fill up the content of the fragment cache entry.
//
if (HttpDataChunkFromMemory == LocalDataChunk.DataChunkType)
{
UlProbeForRead(
LocalDataChunk.FromMemory.pBuffer,
LocalDataChunk.FromMemory.BufferLength,
sizeof(PVOID),
RequestorMode
);
if (FALSE == UlCacheEntrySetData(
pCacheEntry,
(PUCHAR) LocalDataChunk.FromMemory.pBuffer,
(ULONG) Length,
0
))
{
Status = STATUS_INSUFFICIENT_RESOURCES;
goto end;
}
}
else
{
pReadBuffer = (PUCHAR) MmMapLockedPagesSpecifyCache(
pCacheEntry->pMdl,
KernelMode,
MmCached,
NULL,
FALSE,
LowPagePriority
);
if (pReadBuffer)
{
pOffset = (PLARGE_INTEGER)
&LocalDataChunk.FromFileHandle.ByteRange.StartingOffset;
//
// CODEWORK: support async read for file handles opened as
// non-buffered.
//
Status = ZwReadFile(
LocalDataChunk.FromFileHandle.FileHandle,
NULL,
NULL,
NULL,
&IoStatusBlock,
pReadBuffer,
(ULONG) Length,
pOffset,
NULL
);
MmUnmapLockedPages(pReadBuffer, pCacheEntry->pMdl);
}
else
{
Status = STATUS_INSUFFICIENT_RESOURCES;
}
if (!NT_SUCCESS(Status))
{
goto end;
}
}
}
__except( UL_EXCEPTION_FILTER() )
{
Status = UL_CONVERT_EXCEPTION_CODE( GetExceptionCode() );
goto end;
}
//
// Add the fragment cache entry.
//
Status = UlAddCacheEntry(pCacheEntry);
//
// Release the reference count of the cache entry because
// UlAddCacheEntry adds an extra reference in the success case.
//
DEREFERENCE_URI_CACHE_ENTRY(pCacheEntry, CREATE);
//
// Reset pCacheEntry so we won't double-free if UlAddCacheEntry fails.
//
pCacheEntry = NULL;
end:
UlFreeCapturedUnicodeString(&FragmentName);
UlConfigGroupInfoRelease(&UrlInfo);
if (pFileObject)
{
ObDereferenceObject(pFileObject);
}
if (!NT_SUCCESS(Status))
{
if (pCacheEntry)
{
UlFreeCacheEntry(pCacheEntry);
}
}
if (pSanitizedUrl)
{
UL_FREE_POOL(pSanitizedUrl, URL_POOL_TAG);
}
return Status;
} // UlAddFragmentToCache
/***************************************************************************++
Routine Description:
Creates a fragment cache entry.
Arguments:
pProcess - process that is adding the fragment cache entry
pFragmentName - key of the fragment cache entry
FragmentNameLength - length of the fragment name
pBuffer - data to be associated with the fragment cache entry
BufferLength - length of the data
pCachePolicy - specifies the policy of the new fragment cache entry
Return Value:
NTSTATUS
--***************************************************************************/
NTSTATUS
UlpCreateFragmentCacheEntry(
IN PUL_APP_POOL_PROCESS pProcess,
IN PWSTR pFragmentName,
IN ULONG FragmentNameLength,
IN ULONG Length,
IN PHTTP_CACHE_POLICY pCachePolicy,
OUT PUL_URI_CACHE_ENTRY *ppCacheEntry
)
{
PUL_URI_CACHE_ENTRY pCacheEntry;
ULONG Hash;
NTSTATUS Status = STATUS_SUCCESS;
//
// Sanity check.
//
PAGED_CODE();
ASSERT(pCachePolicy);
ASSERT(ppCacheEntry);
if ( HttpCachePolicyTimeToLive == pCachePolicy->Policy
&& 0 == pCachePolicy->SecondsToLive )
{
// A TTL of 0 seconds doesn't make sense. Bail out.
*ppCacheEntry = NULL;
return STATUS_INVALID_PARAMETER;
}
pCacheEntry = UlAllocateCacheEntry(
FragmentNameLength + sizeof(WCHAR),
Length
);
if (pCacheEntry)
{
__try
{
//
// Initialize the cache entry.
//
Hash = HashRandomizeBits(HashStringNoCaseW(pFragmentName, 0));
UlInitCacheEntry(
pCacheEntry,
Hash,
FragmentNameLength,
pFragmentName,
NULL,
NULL,
0
);
pCacheEntry->CachePolicy = *pCachePolicy;
if (pCachePolicy->Policy == HttpCachePolicyTimeToLive)
{
ASSERT( 0 != pCachePolicy->SecondsToLive );
KeQuerySystemTime(&pCacheEntry->ExpirationTime);
if ( pCachePolicy->SecondsToLive > C_SECS_PER_YEAR )
{
// Maximum TTL is 1 year
pCacheEntry->CachePolicy.SecondsToLive = C_SECS_PER_YEAR;
}
//
// Convert seconds to 100 nanosecond intervals (x * 10^7).
//
pCacheEntry->ExpirationTime.QuadPart +=
pCacheEntry->CachePolicy.SecondsToLive * C_NS_TICKS_PER_SEC;
}
else
{
pCacheEntry->ExpirationTime.QuadPart = 0;
}
//
// Remember who created us.
//
pCacheEntry->pProcess = pProcess;
pCacheEntry->pAppPool = pProcess->pAppPool;
//
// Generate the content of the cache entry.
//
pCacheEntry->ContentLength = Length;
}
__except( UL_EXCEPTION_FILTER() )
{
Status = UL_CONVERT_EXCEPTION_CODE(GetExceptionCode());
}
}
else
{
Status = STATUS_NO_MEMORY;
}
if (!NT_SUCCESS(Status))
{
if (pCacheEntry)
{
UlFreeCacheEntry(pCacheEntry);
pCacheEntry = NULL;
}
}
*ppCacheEntry = pCacheEntry;
return Status;
} // UlpCreateFragmentCacheEntry
/***************************************************************************++
Routine Description:
Reads a fragment back from cache.
Arguments:
pProcess - process that is reading the fragment
pInputBuffer - points to a buffer that describes HTTP_READ_FRAGMENT_INFO
InputBufferLength - length of the input buffer
pOutputBuffer - points to a buffer to copy the data from the fragment
cache entry
OutputBufferLength - length of the buffer to copy
pBytesRead - optionally tells how many bytes are copied or needed
Return Value:
NTSTATUS
--***************************************************************************/
NTSTATUS
UlReadFragmentFromCache(
IN PUL_APP_POOL_PROCESS pProcess,
IN PVOID pInputBuffer,
IN ULONG InputBufferLength,
OUT PVOID pOutputBuffer,
IN ULONG OutputBufferLength,
IN KPROCESSOR_MODE RequestorMode,
OUT PULONG pBytesRead
)
{
PUL_URI_CACHE_ENTRY pCacheEntry;
PMDL pMdl;
HTTP_READ_FRAGMENT_INFO ReadInfo;
PUNICODE_STRING pFragmentName;
PHTTP_BYTE_RANGE pByteRange;
PVOID pContentBuffer;
ULONGLONG Offset;
ULONGLONG Length;
ULONGLONG ContentLength;
ULONG ReadLength;
NTSTATUS Status;
UNICODE_STRING FragmentName;
//
// Validate pInputBuffer and InputBufferLength.
//
if (!pInputBuffer || InputBufferLength < sizeof(HTTP_READ_FRAGMENT_INFO))
{
return STATUS_INVALID_PARAMETER;
}
//
// Initialization.
//
pCacheEntry = NULL;
pMdl = NULL;
RtlInitEmptyUnicodeString(&FragmentName, NULL, 0);
__try
{
//
// Capture HTTP_READ_FRAGMENT_INFO into the local ReadInfo.
//
UlProbeForRead(
pInputBuffer,
sizeof(HTTP_READ_FRAGMENT_INFO),
sizeof(PVOID),
RequestorMode
);
ReadInfo = *((PHTTP_READ_FRAGMENT_INFO) pInputBuffer);
pFragmentName = &ReadInfo.FragmentName;
pByteRange = &ReadInfo.ByteRange;
Status = UlProbeAndCaptureUnicodeString(
pFragmentName,
RequestorMode,
&FragmentName,
0
);
if (!NT_SUCCESS(Status))
{
goto end;
}
//
// Check out the fragment cache entry based on key URL passed in.
//
Status = UlCheckoutFragmentCacheEntry(
FragmentName.Buffer,
FragmentName.Length,
pProcess,
&pCacheEntry
);
if (!NT_SUCCESS(Status))
{
goto end;
}
ASSERT(pCacheEntry);
ContentLength = pCacheEntry->ContentLength;
Offset = pByteRange->StartingOffset.QuadPart;
//
// Validate byte range for offset and length.
//
if (Offset >= ContentLength)
{
Status = STATUS_INVALID_PARAMETER;
goto end;
}
if (pByteRange->Length.QuadPart == HTTP_BYTE_RANGE_TO_EOF)
{
Length = ContentLength - Offset;
}
else
{
Length = pByteRange->Length.QuadPart;
}
if (!Length || Length > ULONG_MAX || Length > (ContentLength - Offset))
{
Status = STATUS_INVALID_PARAMETER;
goto end;
}
ASSERT((Length + Offset) <= ContentLength);
ReadLength = (ULONG) Length;
//
// Check if we have enough buffer space, if not, tell the caller the
// exact bytes needed to complete the read.
//
if (OutputBufferLength < ReadLength)
{
*pBytesRead = ReadLength;
Status = STATUS_BUFFER_OVERFLOW;
goto end;
}
//
// Build a partial MDL to read the data.
//
pMdl = UlAllocateMdl(
(PCHAR) MmGetMdlVirtualAddress(pCacheEntry->pMdl) + Offset,
ReadLength,
FALSE,
FALSE,
NULL
);
if (NULL == pMdl)
{
Status = STATUS_INSUFFICIENT_RESOURCES;
goto end;
}
IoBuildPartialMdl(
pCacheEntry->pMdl,
pMdl,
(PCHAR) MmGetMdlVirtualAddress(pCacheEntry->pMdl) + Offset,
ReadLength
);
pContentBuffer = MmGetSystemAddressForMdlSafe(
pMdl,
NormalPagePriority
);
if (NULL == pContentBuffer)
{
Status = STATUS_INSUFFICIENT_RESOURCES;
goto end;
}
//
// Copy the data from the cache entry back to the output buffer.
// UlFreeMdl unmaps the data for partial MDLs so no need to unmap
// if either copy succeeds or an exception is raised.
//
UlProbeForWrite(
pOutputBuffer,
ReadLength,
sizeof(PVOID),
RequestorMode
);
RtlCopyMemory(
pOutputBuffer,
pContentBuffer,
ReadLength
);
//
// Set how many bytes we have copied.
//
*pBytesRead = ReadLength;
Status = STATUS_SUCCESS;
}
__except( UL_EXCEPTION_FILTER() )
{
Status = UL_CONVERT_EXCEPTION_CODE( GetExceptionCode() );
}
end:
UlFreeCapturedUnicodeString(&FragmentName);
if (pMdl)
{
UlFreeMdl(pMdl);
}
if (pCacheEntry)
{
UlCheckinUriCacheEntry(pCacheEntry);
}
return Status;
} // UlReadFragmentFromCache
// Memory allocator front end
/***************************************************************************++
Routine Description:
Allocate a cache entry from paged pool + space for response
from physical memory
Arguments:
SpaceLength - Length of space for URI + ETag + LoggingData
ResponseLength - Length of Response
Return Value:
Pointer to allocated entry or NULL on failure
--***************************************************************************/
PUL_URI_CACHE_ENTRY
UlAllocateCacheEntry(
ULONG SpaceLength,
ULONG ResponseLength
)
{
PUL_URI_CACHE_ENTRY pEntry;
PAGED_CODE();
if(!g_CacheMemEnabled)
return NULL;
// Allocate from LargeMem
pEntry = UL_ALLOCATE_STRUCT_WITH_SPACE(
PagedPool,
UL_URI_CACHE_ENTRY,
SpaceLength,
UL_URI_CACHE_ENTRY_POOL_TAG
);
if( NULL == pEntry ) {
return NULL;
}
RtlZeroMemory(pEntry, sizeof(UL_URI_CACHE_ENTRY));
pEntry->pMdl = UlLargeMemAllocate(ResponseLength);
if( NULL == pEntry->pMdl ) {
UL_FREE_POOL_WITH_SIG( pEntry, UL_URI_CACHE_ENTRY_POOL_TAG );
return NULL;
}
pEntry->NumPages = ROUND_TO_PAGES(ResponseLength) >> PAGE_SHIFT;
return pEntry;
}
/***************************************************************************++
Routine Description:
Free a cache entry
Arguments:
pEntry - Cache Entry to be freed
Return Value:
Nothing
--***************************************************************************/
VOID
UlFreeCacheEntry(
PUL_URI_CACHE_ENTRY pEntry
)
{
PAGED_CODE();
ASSERT( IS_VALID_URI_CACHE_ENTRY(pEntry) );
ASSERT( pEntry->pMdl != NULL );
UlLargeMemFree( pEntry->pMdl );
UL_FREE_POOL_WITH_SIG( pEntry, UL_URI_CACHE_ENTRY_POOL_TAG );
}
/***************************************************************************++
Routine Description:
Turn off the UL cache
--***************************************************************************/
VOID
UlDisableCache(
VOID
)
{
PAGED_CODE();
InterlockedExchange(&g_CacheMemEnabled, FALSE);
} // UlDisableCache
/***************************************************************************++
Routine Description:
Turn on the UL cache
--***************************************************************************/
VOID
UlEnableCache(
VOID
)
{
PAGED_CODE();
InterlockedExchange(&g_CacheMemEnabled, TRUE);
} // UlEnableCache