|
|
/*++
Copyright (c) 2001 Microsoft Corporation
Module Name:
opcache.cxx
Abstract:
Routines implementing a cache of which operations have already been checked for a particular client context
Author:
Cliff Van Dyke (cliffv) 14-Nov-2001
--*/
#include "pch.hxx"
#define AZD_COMPONENT AZD_ACCESS
//
// Windows XP RTM doesn't support RtlLookupElementGenericTableFull
// Windoes XP SP1 does support it
// So avoid the API if building a binary that runs on XP RTM.
#ifndef RUN_ON_XP_RTM
#define USE_AVL_FULL 1
#endif // RUN_ON_XP_RTM
//
// Structure describing the operations that have previously had access checks done on them
// for a particular scope.
//
typedef struct _AZP_OPERATION_CACHE {
//
// Pointer to the scope object this cache entry applies to
// The ReferenceCount is held on Scope.
// This value is NULL if the scope is the application.
//
PAZP_SCOPE Scope;
//
// Pointer to the operation object this cache entry applies to
// The ReferenceCount is held on Operation
// This field must be the first field in the structure.
//
PAZP_OPERATION Operation;
//
// Pointer to the business rule string applicable to this operation
// The actual string is a part of this same allocated buffer.
AZP_STRING BizRuleString;
//
// Result of the access check
//
DWORD Result;
} AZP_OPERATION_CACHE, *PAZP_OPERATION_CACHE;
RTL_GENERIC_COMPARE_RESULTS AzpAvlCacheCompare( IN PRTL_GENERIC_TABLE Table, IN PVOID FirstStruct, IN PVOID SecondStruct ) /*++
Routine Description:
This routine will compare twp operation cache entries
Arguments:
IN PRTL_GENERIC_TABLE - Supplies the table containing the announcements IN PVOID FirstStuct - The first structure to compare. IN PVOID SecondStruct - The second structure to compare.
Return Value:
Result of the comparison.
--*/ { PAZP_OPERATION_CACHE Op1 = (PAZP_OPERATION_CACHE) FirstStruct; PAZP_OPERATION_CACHE Op2 = (PAZP_OPERATION_CACHE) SecondStruct;
if ( Op1->Scope < Op2->Scope ) { return GenericLessThan; } else if ( Op1->Scope > Op2->Scope ) { return GenericGreaterThan; } else if ( Op1->Operation < Op2->Operation ) { return GenericLessThan; } else if ( Op1->Operation > Op2->Operation ) { return GenericGreaterThan; } else { return GenericEqual; }
UNREFERENCED_PARAMETER(Table);
}
VOID AzpInitOperationCache( IN PAZP_CLIENT_CONTEXT ClientContext ) /*++
Routine Description:
Initializes the operation cache for a client context
On entry, AzGlResource must be locked exclusively.
Arguments:
ClientContext - Specifies the client context to initialize the cache for
Return Value:
None
--*/ {
//
// Initialization
//
ASSERT( AzpIsLockedExclusive( &AzGlResource ) );
//
// Initialize the AVL tree of scopes that have been access checked already
//
RtlInitializeGenericTable( &ClientContext->OperationCacheAvlTree, AzpAvlCacheCompare, AzpAvlAllocate, AzpAvlFree, NULL);
}
VOID AzpFlushOperationCache( IN PAZP_CLIENT_CONTEXT ClientContext ) /*++
Routine Description:
Flushes the operation cache for a client context
On entry, AcContext->ClientContext.CritSect must be locked OR AzGlResource must be locked exclusively.
Arguments:
ClientContext - Specifies the client context to flush the cache for
Return Value:
None
--*/ { ULONG i; PAZP_OPERATION_CACHE OperationCache;
//
// Initialization
//
ASSERT( AzpIsCritsectLocked( &ClientContext->CritSect ) || AzpIsLockedExclusive( &AzGlResource ) );
//
// Loop until the OperationCache is empty
//
for (;;) {
//
// Get the first element in the table
//
OperationCache = (PAZP_OPERATION_CACHE) RtlEnumerateGenericTable( &ClientContext->OperationCacheAvlTree, TRUE );
if ( OperationCache == NULL ) { break; }
//
// Dereference the Scope object
//
if ( OperationCache->Scope != NULL ) { ObDereferenceObject( &OperationCache->Scope->GenericObject ); OperationCache->Scope = NULL; }
//
// Dereference the Operation object
//
ObDereferenceObject( &OperationCache->Operation->GenericObject );
//
// Delete the entry
//
RtlDeleteElementGenericTable( &ClientContext->OperationCacheAvlTree, OperationCache ); }
ASSERT (RtlNumberGenericTableElementsAvl(&ClientContext->OperationCacheAvlTree) == 0);
//
// Ditch the arrays of cached parameters
//
if ( ClientContext->UsedParameterNames != NULL ) {
for ( i=0; i<ClientContext->UsedParameterCount; i++) { VariantClear( &ClientContext->UsedParameterNames[i] ); VariantClear( &ClientContext->UsedParameterValues[i] ); }
AzpFreeHeap( ClientContext->UsedParameterNames ); ClientContext->UsedParameterNames = NULL;
// UsedParameterValues is a part of the UsedParameterNames allocated block
ClientContext->UsedParameterValues = NULL; ClientContext->UsedParameterCount = 0;
}
}
BOOLEAN AzpCheckOperationCache( IN PACCESS_CHECK_CONTEXT AcContext ) /*++
Routine Description:
This routine checks checks to see if this access check can be satisified by the cache of operations.
On entry, AcContext->ClientContext.CritSect must be locked. On entry, AzGlResource must be locked Shared.
Arguments:
AcContext - Specifies the context of the user to check group membership of. AcContext is updated to indicate any operations that are know to be allowed or denied.
Return Value:
TRUE - All operations were satisfied from cache
--*/ { ULONG WinStatus; ULONG OpIndex; ULONG i;
PAZP_CLIENT_CONTEXT ClientContext = AcContext->ClientContext; AZP_OPERATION_CACHE TemplateOperationCache = {0}; PAZP_OPERATION_CACHE OperationCache;
//
// Initialization
//
ASSERT( AzpIsLockedShared( &AzGlResource ) ); ASSERT( AzpIsCritsectLocked( &ClientContext->CritSect ) );
//
// Check to ensure we should be using the operation cache
//
// Avoid the cache if any interfaces were passed by the caller
// (This could relaxed. It doesn't make any difference that the interface was
// passed by the caller. It only matters if the interface was actually used.
// So we could set a boolean in CScriptEngine::GetItemInfo and simply not cache
// operations that used the interfaces.)
//
if ( AcContext->Interfaces != NULL ) {
AzPrint(( AZD_ACCESS_MORE, "AzpCheckOperationCache: Operation cache avoided since interfaces passed in\n" )); return FALSE; }
//
// If object cache has changed,
// flush the operation cache.
//
// This code doesn't prevent the object cache from changing *during* the access check
// call. That's fine. It does protect against changes made prior to the access check call.
//
if ( ClientContext->OpCacheSerialNumber != ClientContext->GenericObject.AzStoreObject->OpCacheSerialNumber ) {
AzpFlushOperationCache( ClientContext );
//
// Update the serial number to the new serial number
//
AzPrint(( AZD_ACCESS_MORE, "AzpCheckOperationCache: OpCacheSerialNumber changed from %ld to %ld\n", ClientContext->OpCacheSerialNumber, ClientContext->GenericObject.AzStoreObject->OpCacheSerialNumber ));
ClientContext->OpCacheSerialNumber = ClientContext->GenericObject.AzStoreObject->OpCacheSerialNumber; }
//
// If the cache is empty,
// we're done now
//
if ( RtlNumberGenericTableElementsAvl(&ClientContext->OperationCacheAvlTree) == 0 ) { return FALSE; }
//
// If any of the parmeters used to build the operation cache have changed,
// Don't use the operation cache.
//
//
// We didn't capture the array
// So access it under a try/except
__try {
//
// If the number of passed parameters changed in size,
// flush the cache
//
if ( ClientContext->UsedParameterCount != 0 && ClientContext->UsedParameterCount != AcContext->ParameterCount ) {
AzPrint(( AZD_CRITICAL, "AzpCheckOperationCache: Parameter count changed from previous call %ld %ld\n", ClientContext->UsedParameterCount, AcContext->ParameterCount ));
AzpFlushOperationCache( ClientContext ); }
//
//
// For each name on the existing list of used paramaters,
// check to ensure the value hasn't change
//
for ( i=0; i<ClientContext->UsedParameterCount; i++ ) {
//
// Skip parameters that weren't used on the previous call
//
if ( V_VT(&ClientContext->UsedParameterNames[i] ) == VT_EMPTY ) { continue; }
//
// If the used parameter wasn't passed in on this new call,
// or if the used parameter has a different value on this new call,
// flush the cache
//
// We rely on the fact that the app always passes the same parameter names
// on every AccessCheck call. That is reasonable since the app has a fixed
// contract with the bizrule writers to supply a fixed set of parameters.
//
if ( i >= AcContext->ParameterCount || AzpCompareParameterNames( &ClientContext->UsedParameterNames[i], &AcContext->ParameterNames[i] ) != 0 || VarCmp( &ClientContext->UsedParameterValues[i], &AcContext->ParameterValues[i], LOCALE_USER_DEFAULT, 0 ) != (HRESULT)VARCMP_EQ ) {
AzPrint(( AZD_ACCESS_MORE, "AzpCheckOperationCache: Parameter '%ws' changed from previous call\n", V_BSTR( &ClientContext->UsedParameterNames[i] ) ));
AzpFlushOperationCache( ClientContext ); break; } }
} __except( EXCEPTION_EXECUTE_HANDLER ) {
AzPrint((AZD_CRITICAL, "AzpUpdateOperationCache took an exception: 0x%lx\n", GetExceptionCode())); return FALSE; }
//
// Loop handling each operation
//
for ( OpIndex=0; OpIndex<AcContext->OperationCount; OpIndex++ ) {
//
// Lookup the scope/operation pair in the operation cache
//
TemplateOperationCache.Scope = AcContext->Scope; TemplateOperationCache.Operation = AcContext->OperationObjects[OpIndex];
OperationCache = (PAZP_OPERATION_CACHE) RtlLookupElementGenericTable ( &ClientContext->OperationCacheAvlTree, &TemplateOperationCache );
if ( OperationCache == NULL ) { continue; }
//
// Return the bizrule string for this operation
// The caller cannot depend upon order of evaluation.
// Therefore, only the first cached string need be returned.
//
if ( AcContext->BusinessRuleString.StringSize == 0 ) {
WinStatus = AzpDuplicateString( &AcContext->BusinessRuleString, &OperationCache->BizRuleString );
if ( WinStatus != NO_ERROR ) { return FALSE; }
}
//
// The operation result was found,
// return it.
//
AcContext->Results[OpIndex] = OperationCache->Result; AcContext->OperationWasProcessed[OpIndex] = TRUE; AcContext->ProcessedOperationCount++; AcContext->CachedOperationCount++;
AzPrint(( AZD_ACCESS_MORE, "AzpCheckOperationCache: '%ws/%ws' found in operation cache\n", OperationCache->Scope != NULL ? OperationCache->Scope->GenericObject.ObjectName->ObjectName.String : NULL, OperationCache->Operation->GenericObject.ObjectName->ObjectName.String, OperationCache->Result ));
if (AcContext->OperationCount == AcContext->CachedOperationCount) { return TRUE; }
}
return FALSE;
}
VOID AzpUpdateOperationCache( IN PACCESS_CHECK_CONTEXT AcContext ) /*++
Routine Description:
This routine updated the operation cache with new results.
On entry, AcContext->ClientContext.CritSect must be locked. On entry, AzGlResource must be locked Shared.
Arguments:
AcContext - Specifies the context of the user to check group membership of. AcContext is updated to indicate any operations that are know to be allowed or denied.
Return Value:
None
--*/ { ULONG OpIndex; PAZP_CLIENT_CONTEXT ClientContext = AcContext->ClientContext;
HRESULT hr; ULONG i;
#ifdef USE_AVL_FULL
PVOID NodeOrParent; TABLE_SEARCH_RESULT SearchResult; #endif USE_AVL_FULL
BOOLEAN NewElement;
PAZP_OPERATION_CACHE TemplateOperationCache = NULL; ULONG TemplateOperationCacheSize; PAZP_OPERATION_CACHE OperationCache;
//
// Initialization
//
ASSERT( AzpIsLockedShared( &AzGlResource ) ); ASSERT( AzpIsCritsectLocked( &ClientContext->CritSect ) );
//
// If we aren't supposed to use the cache for this AccessCheck,
// we're done
//
if ( AcContext->Interfaces != NULL ) { goto Cleanup; }
//
// If all operations were satisfied via the cache,
// simply return
//
if ( AcContext->CachedOperationCount == AcContext->OperationCount ) {
AzPrint(( AZD_ACCESS_MORE, "AzpUpdateOperationCache: No operations to cache\n" )); goto Cleanup; }
//
// Save the list of "used" parameters
//
// This is a combined list of all parameters used on this call and
// all the parameters used on previous calls.
//
// Skip this if there are no newly used parameters
//
if ( AcContext->UsedParameterCount != 0 ) {
//
// We didn't capture the array
// So access it under a try/except
__try {
ASSERT( AcContext->ParameterCount != 0 ); ASSERT( ClientContext->UsedParameterCount == 0 || ClientContext->UsedParameterCount == AcContext->ParameterCount );
//
// If no buffer has been allocated yet,
// allocate and initialize it.
//
if ( ClientContext->UsedParameterCount == 0 ) {
VARIANT *ParameterNames = NULL;
//
// Allocate the array
//
ParameterNames = (VARIANT *) AzpAllocateHeap( 2 * sizeof(VARIANT) * AcContext->ParameterCount, "OPCACHE" );
if ( ParameterNames == NULL ) { goto Cleanup; }
//
// Initialize all of the variants to VT_EMPTY
//
for ( i=0; i<AcContext->ParameterCount*2; i++ ) {
VariantInit( &ParameterNames[i] ); }
//
// Store the pointers to the initialized arrays
//
ClientContext->UsedParameterNames = ParameterNames; ClientContext->UsedParameterValues = &ParameterNames[AcContext->ParameterCount]; ClientContext->UsedParameterCount = AcContext->ParameterCount; }
//
// Copy the new names into the new buffer
//
for ( i=0; i<AcContext->ParameterCount; i++ ) {
//
// Only copy parameters that have been used
// and weren't copy on a previous call
//
if ( AcContext->UsedParameters[i] && V_VT(&ClientContext->UsedParameterNames[i]) == VT_EMPTY ) {
hr = VariantCopy( &ClientContext->UsedParameterNames[i], &AcContext->ParameterNames[i] );
if ( FAILED(hr) ) { goto Cleanup; }
hr = VariantCopy( &ClientContext->UsedParameterValues[i], &AcContext->ParameterValues[i] );
if ( FAILED(hr) ) { VariantClear( &ClientContext->UsedParameterNames[i] ); goto Cleanup; }
AzPrint(( AZD_ACCESS_MORE, "AzpUpdateOperationCache: Added parameter '%ws' to the used parameter list\n", V_BSTR( &ClientContext->UsedParameterNames[i] ) ));
} }
} __except( EXCEPTION_EXECUTE_HANDLER ) {
hr = GetExceptionCode(); AzPrint((AZD_CRITICAL, "AzpUpdateOperationCache took an exception: 0x%lx\n", hr)); goto Cleanup; }
}
//
// Allocate a template for the operation cache entry
//
TemplateOperationCacheSize = sizeof(AZP_OPERATION_CACHE) + AcContext->BusinessRuleString.StringSize; SafeAllocaAllocate( TemplateOperationCache, TemplateOperationCacheSize );
if ( TemplateOperationCache == NULL ) { goto Cleanup; }
//
// Loop handling each operation
//
for ( OpIndex=0; OpIndex<AcContext->OperationCount; OpIndex++ ) {
//
// Lookup the scope/operation pair in the operation cache
//
TemplateOperationCache->Scope = AcContext->Scope; TemplateOperationCache->Operation = AcContext->OperationObjects[OpIndex];
AzPrint(( AZD_ACCESS_MORE, "AzpUpdateOperationCache: Added '%ws/%ws' %ld to operation cache\n", AcContext->Scope != NULL ? AcContext->Scope->GenericObject.ObjectName->ObjectName.String : NULL, AcContext->OperationObjects[OpIndex]->GenericObject.ObjectName->ObjectName.String, AcContext->Results[OpIndex] ));
OperationCache = (PAZP_OPERATION_CACHE) #ifdef USE_AVL_FULL
RtlLookupElementGenericTableFull( #else // USE_AVL_FULL
RtlLookupElementGenericTable( #endif // USE_AVL_FULL
&ClientContext->OperationCacheAvlTree, TemplateOperationCache #ifdef USE_AVL_FULL
, &NodeOrParent, &SearchResult #endif // USE_AVL_FULL
);
if ( OperationCache == NULL ) {
OperationCache = (PAZP_OPERATION_CACHE) #ifdef USE_AVL_FULL
RtlInsertElementGenericTableFull( #else // USE_AVL_FULL
RtlInsertElementGenericTable( #endif // USE_AVL_FULL
&ClientContext->OperationCacheAvlTree, TemplateOperationCache, TemplateOperationCacheSize, &NewElement #ifdef USE_AVL_FULL
, NodeOrParent, SearchResult #endif // USE_AVL_FULL
);
if ( OperationCache == NULL ) { continue; }
ASSERT( NewElement );
//
// Initialize the new element
//
if ( OperationCache->Scope != NULL) { InterlockedIncrement( &OperationCache->Scope->GenericObject.ReferenceCount ); AzpDumpGoRef( "Scope Cache", &OperationCache->Scope->GenericObject ); }
InterlockedIncrement( &OperationCache->Operation->GenericObject.ReferenceCount ); AzpDumpGoRef( "Operation Cache", &OperationCache->Operation->GenericObject );
OperationCache->Result = AcContext->Results[OpIndex];
//
// Fill in the biz rule string
// Don't bother if the result is NO_ERROR.
//
if ( OperationCache->Result != NO_ERROR ) { OperationCache->BizRuleString.String = (LPWSTR)&OperationCache[1]; OperationCache->BizRuleString.StringSize = AcContext->BusinessRuleString.StringSize;
if ( AcContext->BusinessRuleString.StringSize != 0 ) { RtlCopyMemory( OperationCache->BizRuleString.String, AcContext->BusinessRuleString.String, AcContext->BusinessRuleString.StringSize ); } } else { OperationCache->BizRuleString.StringSize = 0; }
//
// The operation is already cached.
// This is one of two cases:
// * The result was already filled in by AzpCheckOperationCache
// * The caller passed the same operation in twice
//
// In the latter case, the cache is assumed to be correct. Return the
// cached value for the all results.
//
} else { AcContext->Results[OpIndex] = OperationCache->Result; }
}
//
// Free locally used resources
//
Cleanup:
SafeAllocaFree( TemplateOperationCache ); return;
}
|