|
|
//+---------------------------------------------------------------------------
//
// Microsoft Windows
// Copyright (C) Microsoft Corporation, 1996 - 2000.
//
// File: wqcache.cxx
//
// Contents: WEB Query cache class
//
// History: 96/Jan/3 DwightKr Created
//
//----------------------------------------------------------------------------
#include <pch.cxx>
#pragma hdrstop
#include <perfobj.hxx>
#include <params.hxx>
DECLARE_INFOLEVEL(ciGib);
//+---------------------------------------------------------------------------
//
// Function: GetSecurityToken
//
// Synopsis: Gets the security token handle for the current thread
//
// History: 96-Jan-18 DwightKr Created
//
//---------------------------------------------------------------------------
HANDLE GetSecurityToken(TOKEN_STATISTICS & TokenInformation) { HANDLE hToken; NTSTATUS status = NtOpenThreadToken( GetCurrentThread(), TOKEN_QUERY | TOKEN_IMPERSONATE | TOKEN_DUPLICATE, TRUE, // OpenAsSelf
&hToken );
if ( !NT_SUCCESS( status ) ) return INVALID_HANDLE_VALUE;
DWORD ReturnLength; status = NtQueryInformationToken( hToken, TokenStatistics, (LPVOID)&TokenInformation, sizeof TokenInformation, &ReturnLength );
if ( !NT_SUCCESS( status ) ) { NtClose( hToken ); ciGibDebugOut(( DEB_ERROR, "NtQueryInformationToken failed, 0x%x\n", status )); THROW( CException( status )); }
Win4Assert( TokenInformation.TokenType == TokenImpersonation );
return hToken; }
//+---------------------------------------------------------------------------
//
// Member: CWQueryBookmark::CWQueryBookmark - public constructor
//
// Synopsis: Reads the values from a bookmark into the member variables
//
// History: 96-Jan-18 DwightKr Created
//
//----------------------------------------------------------------------------
CWQueryBookmark::CWQueryBookmark( WCHAR const * wcsBookmark ) { Win4Assert( 0 != wcsBookmark );
//
// Bookmarks have the following format:
//
// {S|N}-ptr_to_record-sequence_number-row_number
//
// Eg:
//
// S-1b234a-250-100
// 0123456789 123456789 1234567
//
// S = sequential cursor
// 001b234a = address of CWQueryItem containing query results, in hex
// 00000250 = sequence number, in hex
// 00000010 = next row # to display, in hex
//
//
// N-1b277a-a50-2a000
// 0123456789 123456789 1234567
//
// N = non-sequential cursor
// 001b277a = address of CWQueryItem containing query results, in hex
// 00000a50 = sequence number, in hex
// 0002a000 = next row # to display, in hex
//
// Bookmarks are a maximum of 34 characters long, but they may be shorter.
// On 32 bit platforms, the maximum length is 28 characters.
WCHAR * wcsPtr = (WCHAR *)wcsBookmark;
//
// Verify that the bookmark is well formed. There should be 3 hyphens
// and the length must be at least 24 characters; there may be trailing
// spaces.
//
if ( (*(wcsBookmark+1) != L'-') ) { THROW( CException( DB_E_ERRORSINCOMMAND ) ); }
if ( *wcsBookmark == L'S' ) { _fSequential = TRUE; } else if ( *wcsBookmark == L'N' ) { _fSequential = FALSE; } else { THROW( CException( DB_E_ERRORSINCOMMAND ) ); }
WCHAR *pwcStart = wcsPtr + 2;
#if defined(_WIN64)
_pItem = (CWQueryItem *) _wcstoui64( pwcStart, &wcsPtr, 16 ); #else
_pItem = (CWQueryItem *) wcstoul( pwcStart, &wcsPtr, 16 ); #endif
if ( ( pwcStart == wcsPtr ) || ( *wcsPtr++ != L'-' ) ) THROW( CException( DB_E_ERRORSINCOMMAND ) );
pwcStart = wcsPtr; _ulSequenceNumber = wcstoul( wcsPtr, &wcsPtr, 16 );
if ( ( pwcStart == wcsPtr ) || ( *wcsPtr++ != L'-' ) ) THROW( CException( DB_E_ERRORSINCOMMAND ) );
pwcStart = wcsPtr; _lRecordNumber = wcstol( wcsPtr, &wcsPtr, 16 ); if ( ( pwcStart == wcsPtr ) || ( 0 != *wcsPtr ) ) THROW( CException( DB_E_ERRORSINCOMMAND ) );
unsigned cbBmk = (unsigned)(wcsPtr - wcsBookmark + 1) * sizeof (WCHAR);
if ( cbBmk >= sizeof( _wcsBookmark ) ) THROW( CException( DB_E_ERRORSINCOMMAND ) );
RtlCopyMemory( _wcsBookmark, wcsBookmark, cbBmk );
//Win4Assert( 0 == _wcsBookmark[ cbBmk / ( sizeof WCHAR ) ] );
}
//+---------------------------------------------------------------------------
//
// Functon: AppendHex64Number, inline
//
// Synopsis: Append a 64 bit hex value to a wide string.
//
// Arguments: [pwc] - string to append number to
// [x] - value to add to string
//
// Returns: Pointer to next character in string to be filled in
//
// History: 1998 Nov 06 AlanW Created header; return pwc
//
//----------------------------------------------------------------------------
inline WCHAR * AppendHex64Number( WCHAR * pwc, ULONG_PTR x ) {
#if defined(_WIN64)
_i64tow( x, pwc, 16 ); #else
_itow( x, pwc, 16 ); #endif
pwc += wcslen( pwc );
return pwc; }
//+---------------------------------------------------------------------------
//
// Functon: AppendHexNumber, inline
//
// Synopsis: Append a hex value to a wide string.
//
// Arguments: [pwc] - string to append number to
// [x] - value to add to string
//
// Returns: Pointer to next character in string to be filled in
//
// History: 96 Apr 09 AlanW Created header; return pwc
//
//----------------------------------------------------------------------------
inline WCHAR * AppendHexNumber( WCHAR * pwc, ULONG x ) { _itow( x, pwc, 16 ); pwc += wcslen( pwc );
return pwc; }
//+---------------------------------------------------------------------------
//
// Method: CWQueryBookmark::CWQueryBookmark, public
//
// Synopsis: Constructs a query bookmark
//
// Arguments: [fSequential] - TRUE if a sequential query
// [pItem] - the query
// [ulSequenceNumber] - # of queries executed so far
// [lRecordNumber] - starting record number of the bookmark
//
// History: 96 DwightKr Created
// 97 Apr 20 dlee Created header
//
//----------------------------------------------------------------------------
CWQueryBookmark::CWQueryBookmark( BOOL fSequential, CWQueryItem * pItem, ULONG ulSequenceNumber, LONG lRecordNumber ) : _fSequential( fSequential ), _pItem( pItem ), _ulSequenceNumber( ulSequenceNumber ), _lRecordNumber( lRecordNumber ) { WCHAR *pwc = _wcsBookmark;
*pwc++ = _fSequential ? L'S' : L'N'; *pwc++ = L'-';
pwc = AppendHex64Number( pwc, (ULONG_PTR) _pItem ); *pwc++ = L'-';
pwc = AppendHexNumber( pwc, _ulSequenceNumber ); *pwc++ = L'-';
pwc = AppendHexNumber( pwc, _lRecordNumber ); *pwc = 0; }
//+---------------------------------------------------------------------------
//
// Member: CWQueryCache::CWQueryCache - public constructor
//
// Synopsis: Initializes the linked list of query items, and initializes
// the query sequence counter.
//
// History: 96-Jan-18 DwightKr Created
//
//----------------------------------------------------------------------------
CWQueryCache::CWQueryCache() : _ulSignature( LONGSIG( 'q', 'c', 'a', 'c' ) ), _cRequestsRejected( 0 ), _ulSequenceNumber( 0 ), _cActiveRequests( 0 ), _pendingQueue( 512 ), // large enough so realloc never happens
#pragma warning( disable : 4355 ) // this used in base initialization
_threadWatchDog( WatchDogThread, this, TRUE ) #pragma warning( default : 4355 )
{ //
// Create a security descriptor for the shared memory. The security
// descriptor gives full access to the shared memory for the creator
// and read acccess for everyone else. By default, only the creator
// can access the shared memory. But we want that anyone will be able
// to read the performance data. So we must give read access to
// everyone.
//
SECURITY_DESCRIPTOR sd; BOOL f = InitializeSecurityDescriptor( &sd, SECURITY_DESCRIPTOR_REVISION ); if ( !f ) THROW( CException() );
HANDLE hToken; f = OpenProcessToken( GetCurrentProcess(), TOKEN_QUERY, &hToken ); if ( !f ) THROW( CException() );
SWin32Handle xHandle( hToken );
DWORD cbTokenInfo; f = GetTokenInformation( hToken, TokenOwner, 0, 0, &cbTokenInfo ); if ( ( !f ) && ( ERROR_INSUFFICIENT_BUFFER != GetLastError() ) ) THROW( CException() );
XArray<BYTE> xTo( cbTokenInfo ); TOKEN_OWNER *pTO = (TOKEN_OWNER*)(char*) xTo.Get(); f = GetTokenInformation( hToken, TokenOwner, pTO, cbTokenInfo, &cbTokenInfo ); if ( !f ) THROW( CException() );
SID_IDENTIFIER_AUTHORITY WorldSidAuth = SECURITY_WORLD_SID_AUTHORITY; CSid sidWorld( WorldSidAuth, SECURITY_WORLD_RID );
DWORD cbAcl = sizeof(ACL) + 2 * (sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD)) + GetLengthSid( sidWorld.Get() ) + GetLengthSid( pTO->Owner ); XArray<BYTE> xDacl( cbAcl ); PACL pDacl = (PACL)(char*) xDacl.Get();
f = InitializeAcl( pDacl, cbAcl, ACL_REVISION ); if ( !f ) THROW( CException() );
f = AddAccessAllowedAce( pDacl, ACL_REVISION, FILE_MAP_READ, sidWorld.Get() ); if ( !f ) THROW( CException() );
f = AddAccessAllowedAce( pDacl, ACL_REVISION, FILE_MAP_ALL_ACCESS, pTO->Owner ); if ( !f ) THROW( CException() );
f = SetSecurityDescriptorDacl( &sd, TRUE, pDacl, TRUE ); if ( !f ) THROW( CException() );
SECURITY_ATTRIBUTES sa; sa.nLength = sizeof(sa); sa.lpSecurityDescriptor = &sd; sa.bInheritHandle = FALSE;
_smPerf.CreateForWriteFromSA( CI_ISAPI_PERF_SHARED_MEM, CI_ISAPI_SIZE_OF_COUNTER_BLOCK, sa );
// CreateForWrite throws on failure, so it's OK by this point
Win4Assert( _smPerf.Ok() );
//
// Always write to the "spare" entry, them CopyMemory to the actual
// perfcounters in the watchdog thread from time to time.
//
CI_ISAPI_COUNTERS * pc = &_smSpare;
_pcCacheItems = &pc->_cCacheItems; _pcCacheHits = &pc->_cCacheHits; _pcCacheMisses = &pc->_cCacheMisses; _pcRunningQueries = &pc->_cRunningQueries; _pcCacheHitsAndMisses = &pc->_cCacheHitsAndMisses; _pcTotalQueries = &pc->_cTotalQueries; _pcRequestsQueued = &pc->_cRequestsQueued; _pcRequestsRejected = (ULONG *) &pc->_cRequestsRejected; _pcQueriesPerMinute = &pc->_cQueriesPerMinute;
*_pcCacheItems = 0; *_pcCacheHits = 0; *_pcCacheMisses = 0; *_pcRunningQueries = 0; *_pcCacheHitsAndMisses = 0; *_pcTotalQueries = 0; *_pcRequestsQueued = 0; *_pcRequestsRejected = 0; *_pcQueriesPerMinute = 0;
CopyMemory( _smPerf.GetPointer(), &_smSpare, sizeof _smSpare );
ULONG ulOffset = 0;
for (unsigned i=0; i<MAX_QUERY_COLUMNS; i++) { g_aDbBinding[i].iOrdinal = i+1; // Column #
g_aDbBinding[i].obValue = ulOffset; // Offset of data
g_aDbBinding[i].obLength = 0; // Offset where length data is stored
g_aDbBinding[i].obStatus = 0; // Status info for column written
g_aDbBinding[i].pTypeInfo = 0; // Reserved
g_aDbBinding[i].pObject = 0; // DBOBJECT structure
g_aDbBinding[i].pBindExt = 0; // Ignored
g_aDbBinding[i].dwPart = DBPART_VALUE; // Return data
g_aDbBinding[i].dwMemOwner = DBMEMOWNER_PROVIDEROWNED; // Memory owner
g_aDbBinding[i].eParamIO = 0; // eParamIo
g_aDbBinding[i].cbMaxLen = sizeof(PROPVARIANT *); // Size of data to return
g_aDbBinding[i].dwFlags = 0; // Reserved
g_aDbBinding[i].wType = DBTYPE_VARIANT | DBTYPE_BYREF; // Type of return data
g_aDbBinding[i].bPrecision = 0; // Precision to use
g_aDbBinding[i].bScale = 0; // Scale to use
ulOffset += sizeof(COutputColumn); }
_threadWatchDog.Resume(); }
//+---------------------------------------------------------------------------
//
// Member: CWQueryCache::~CWQueryCache - public destructor
//
// Synopsis: Deletes all query items in the linked list.
//
// History: 96-Jan-18 DwightKr Created
//
//----------------------------------------------------------------------------
CWQueryCache::~CWQueryCache() { Win4Assert( _pendingQueue.Count() == 0 ); Win4Assert( IsEmpty() ); }
//+---------------------------------------------------------------------------
//
// Member: CWQueryCache::ThreadWatchDog - private
//
// Synopsis: The watchdog thread; it periodically:
// moves queries from the pending queue to the active cache
// moves queries from the active cache to the done cache
// flushes old queries from the done cache
// updates values from the registry.
//
// History: 96-Feb-26 DwightKr Created
//
//----------------------------------------------------------------------------
DWORD WINAPI CWQueryCache::WatchDogThread( void *self ) { CWQueryCache &me = * (CWQueryCache *) self;
while ( !fTheActiveXSearchShutdown ) { TRY { me.ProcessCacheEvents(); } CATCH ( CException, e ) { ciGibDebugOut(( DEB_IWARN, "Watchdog thread in CWQueryCache caught exception 0x%x\n", e.GetErrorCode() )); } END_CATCH }
ciGibDebugOut(( DEB_WARN, "Watchdog thread in CWQueryCache is terminating\n" ));
//
// This is only necessary if thread is terminated from DLL_PROCESS_DETACH.
//
//TerminateThread( me._threadWatchDog.GetHandle(), 0 );
return 0; }
//+---------------------------------------------------------------------------
//
// Member: CWQueryCache::ProcessCacheEvents - private
//
// Synopsis: Process events such a cache flushes & completed asynchronous
// queries.
//
// History: 96-Feb-26 DwightKr Created
// 96-Mar-06 DwightKr Added asynchronous query completion
// 86-May-16 DwightKr Add registry change event
//
//----------------------------------------------------------------------------
void CWQueryCache::ProcessCacheEvents() { //
// Setup for Queries / Minute
//
ULONG cStartTotal = Total(); time_t ttStartTotal = time(0); time_t ttLastCachePurge = ttStartTotal; ULONG cTicks = GetTickCount();
CRegChangeEvent regChangeEvent(wcsRegAdminTree);
HANDLE waitHandles[2]; waitHandles[0] = _eventCacheWork.GetHandle(); waitHandles[1] = regChangeEvent.GetEventHandle();
BOOL fBusy = FALSE;
while ( !fTheActiveXSearchShutdown ) { ULONG ulWaitTime;
//
// Set the wait period depending on the existence of any
// asynchronous queries.
//
if ( fBusy ) { // sleep very little for queries to complete
ulWaitTime = 10; } else { if ( ( _pendingQueue.Count() > 0 ) || ( TheWebPendingRequestQueue.Any() ) ) { ulWaitTime = 100; } else { // If we're only processing sequential queries, we need
// to wake up often to update perfcounters.
ulWaitTime = 1000; } }
ULONG res = WaitForMultipleObjects( 2, waitHandles, FALSE, ulWaitTime );
if ( 1 == res ) { TheIDQRegParams.Refresh(); regChangeEvent.Reset(); } else { // time() is expensive, GetTickCount() is cheap
if ( ( GetTickCount() - cTicks ) > 30000 ) { cTicks = GetTickCount(); time_t ttCurrent = time(0);
//
// Compute Queries / Minute
//
time_t deltaTime = ttCurrent - ttStartTotal;
if ( deltaTime >= 30 ) { ULONG deltaTotal = Total() - cStartTotal; *_pcQueriesPerMinute = (ULONG)((deltaTotal * 60) / deltaTime);
//
// Start over every 15 minutes
//
if ( deltaTime > 15 * 60 ) { cStartTotal = Total(); ttStartTotal = time(0); } }
//
// Calculate the elapsed time since we deleted old queries.
// If sufficient time has elapsed, flush unused old queries
// from the cache.
//
if ( (ttCurrent - ttLastCachePurge) >= ((time_t) TheIDQRegParams.GetISCachePurgeInterval() * 60) ) {
ciGibDebugOut(( DEB_ITRACE, "Waking up query-cache purge thread\n" ));
DeleteOldQueries(); _idqFileList.DeleteZombies(); _htxFileList.DeleteZombies(); TheICommandCache.Purge(); TheFormattingCache.Purge();
time(&ttLastCachePurge); } }
// Check for any completed asynchronous queries
// Re-check for any completed asynchronous queries
fBusy = CheckForCompletedQueries(); fBusy |= CheckForPendingRequests(); fBusy |= CheckForCompletedQueries();
_eventCacheWork.Reset(); }
// Update perfcounters
CopyMemory( _smPerf.GetPointer(), &_smSpare, sizeof _smSpare ); } } //ProcessCacheEvents
//+---------------------------------------------------------------------------
//
// Member: CWQueryCache::UpdatePendingRequestCount - public
//
// Synopsis: Updates the pending request statistic
//
// History: 96-May-14 Alanw Created
//
//----------------------------------------------------------------------------
void CWQueryCache::UpdatePendingRequestCount() { *_pcRequestsQueued = (ULONG) TheWebPendingRequestQueue.Count(); }
//+---------------------------------------------------------------------------
//
// Member: CWQueryCache::CheckForPendingRequests - private
//
// Synopsis: Check pending for requests and process them
//
// Returns: TRUE if any pending requests were processed
//
// History: 96-Apr-12 dlee Created
//
//----------------------------------------------------------------------------
BOOL CWQueryCache::CheckForPendingRequests() { // While the pending query queue is getting small in relation to the
// number of threads processing the queries, move a pending request to
// the pending query queue (or process it outright if it's synchronous).
// Only process up to 8 queries -- the thread has other work to do too!
ULONG cTwiceMaxThreads = 2 * TheIDQRegParams.GetMaxActiveQueryThreads(); ULONG cProcessed = 0;
while ( ( cProcessed < 8 ) && ( TheWebPendingRequestQueue.Any() ) && ( cTwiceMaxThreads >= _pendingQueue.Count() ) && ( !fTheActiveXSearchShutdown ) ) { ciGibDebugOut(( DEB_GIB_REQUEST, "Processing a pending request\n" ));
CWebPendingItem item;
// any items in the pending request queue?
if ( !TheWebPendingRequestQueue.AcquireTop( item ) ) return ( 0 != cProcessed );
cProcessed++;
CWebServer webServer( item.GetEcb() );
Win4Assert( webServer.GetHttpStatus() == HTTP_STATUS_ACCEPTED ); UpdatePendingRequestCount();
// Process the request and complete the session if the query
// was synchronous or if the parsing failed. If the status is
// pending, the request is owned by the pending query queue.
// Impersonate the user specified by the client's browser
// Note: the constructor of CImpersonateClient cannot throw, or
// the ecb will be leaked.
CImpersonateClient impersonate( item.GetSecurityToken() );
DWORD hseStatus = ProcessWebRequest( webServer );
if ( HSE_STATUS_PENDING != hseStatus ) { DecrementActiveRequests(); ciGibDebugOut(( DEB_GIB_REQUEST, "Released session in CheckForPendingRequests, active: %d\n", _cActiveRequests )); ciGibDebugOut(( DEB_ITRACE, "releasing session hse %d http %d\n", hseStatus, HTTP_STATUS_OK ));
webServer.SetHttpStatus( HTTP_STATUS_OK ); webServer.ReleaseSession( hseStatus ); }
//
// The destructor of CImpersonateClient will call RevertToSelf()
// thus restoring our privledge level, and the destructor of
// CWebPendingItem will close the security token handle.
//
}
return ( 0 != cProcessed ); } //CheckForPendingRequests
//+---------------------------------------------------------------------------
//
// Member: CWQueryCache::CheckForCompletedQueries - private
//
// Synopsis: Check for completed asynchronous queries
//
// Returns: TRUE if any completed queries were processed, FALSE otherwise
//
// History: 96-Mar-04 DwightKr Created
//
//----------------------------------------------------------------------------
BOOL CWQueryCache::CheckForCompletedQueries() { // shortcut for all-sequential query case
if ( 0 == _pendingQueue.Count() ) return FALSE;
BOOL fDidWork = FALSE; XPtr<CWPendingQueryItem> pendingQuery;
do { pendingQuery.Free();
// Try to find a completed asynchronous query.
// The snapshot code is necessary so IsQueryDone() isn't called
// while holding the lock, as it can take a long time.
{ CLock shutdownLock( _mutexShutdown );
// snapshot the pending query list
XArray<CWPendingQueryItem *> xItems;
{ CLock lock( _mutex );
xItems.Init( _pendingQueue.Count() );
for ( unsigned i = 0; i < xItems.Count(); i++ ) xItems[ i ] = _pendingQueue[ i ]; }
unsigned iPos;
// look for a completed query
for ( unsigned i = 0; pendingQuery.IsNull() && i < xItems.Count(); i++ ) { TRY { if ( xItems[i]->IsQueryDone() ) { iPos = i; pendingQuery.Set( xItems[i] ); } } CATCH( CException, e ) { // the query is in an error state. pass it on below
// so that its state will be reported.
iPos = i; pendingQuery.Set( xItems[i] ); } END_CATCH }
// Remove the query (if found) from the pending query queue.
if ( !pendingQuery.IsNull() ) { CLock lock( _mutex );
// iPos must be accurate since new pending queries are
// appended to the array, and this is the only place
// items are removed.
Win4Assert( _pendingQueue[ iPos ] == pendingQuery.GetPointer() ); _pendingQueue.Remove( iPos ); } }
//
// If we found a completed query, output its results
//
if ( !pendingQuery.IsNull() ) { CWQueryItem * pQueryItem = 0;
TRY { ciGibDebugOut(( DEB_ITRACE, "An asynchronous web query has completed\n" ));
fDidWork = TRUE; SCODE status = S_OK;
CVariableSet & variableSet = pendingQuery->GetVariableSet(); COutputFormat & outputFormat = pendingQuery->GetOutputFormat();
Win4Assert( HTTP_STATUS_ACCEPTED == outputFormat.GetHttpStatus() );
BOOL fCanonic = FALSE;
CVirtualString vString( 16384 );
TRY { // Note: this acquire doesn't acquire the ecb
pQueryItem = pendingQuery->GetPendingQueryItem(); AddToCache( pQueryItem ); pendingQuery->AcquirePendingQueryItem();
Win4Assert( !pQueryItem->IsCanonicalOutput() );
pQueryItem->OutputQueryResults( variableSet, outputFormat, vString ); } CATCH( CException, e ) { status = e.GetErrorCode(); } END_CATCH
if ( S_OK != status ) { vString.Empty();
GetErrorPageNoThrow( eDefaultISAPIError, status, 0, pQueryItem->GetIDQFileName(), & variableSet, & outputFormat, outputFormat.GetLCID(), outputFormat, vString ); }
if ( !fCanonic ) outputFormat.WriteClient( vString ); } CATCH( CException, e ) { // Ignore failures writing to the web server, likely
// out of memory converting output to narrow string.
} END_CATCH
Release( pQueryItem );
// note: the ecb will be released when pendingQuery is freed
// above in the loop or when we leave scope
} else { break; } } while ( !fTheActiveXSearchShutdown );
return fDidWork; } //CheckForCompletedQueries
//+---------------------------------------------------------------------------
//
// Member: CWQueryCache::CreateOrFindQuery - public
//
// Synopsis: Locates the query item specified by the variables
//
// Arguments: [wcsIDQFile] - name of the IDQ file containing the query
// [variableSet] - parameters describing the query to find
// [outputFormat] - format of #'s & dates
// [securityIdentity] - User session for this query
// [fAsynchronous] - was the query asynchronous
//
// Returns: query item matching; 0 otherwise
//
// History: 96-Jan-18 DwightKr Created
//
//----------------------------------------------------------------------------
CWQueryItem * CWQueryCache::CreateOrFindQuery( WCHAR const * wcsIDQFile, XPtr<CVariableSet> & variableSet, XPtr<COutputFormat> & outputFormat, CSecurityIdentity securityIdentity, BOOL & fAsynchronous ) { XInterface<CWQueryItem> xQueryItem;
CVariable * pVarBookmark = variableSet->Find( ISAPI_CI_BOOKMARK );
if ( 0 != pVarBookmark ) { //
// Get the bookmark & skipcount if specified
//
ULONG cwcValue; CWQueryBookmark bookMark( pVarBookmark->GetStringValueRAW() );
LONG lSkipCount = 0; CVariable * pVarSkipCount = variableSet->Find( ISAPI_CI_BOOKMARK_SKIP_COUNT ); if ( 0 != pVarSkipCount ) { lSkipCount = IDQ_wtol( pVarSkipCount->GetStringValueRAW() ); }
xQueryItem.Set( FindItem( bookMark, lSkipCount, securityIdentity ) ); }
if ( !xQueryItem.IsNull() ) { XArray<WCHAR> wcsLocale; LCID lcid = GetQueryLocale( xQueryItem->GetIDQFile().GetLocale(), variableSet.GetReference(), outputFormat.GetReference(), wcsLocale );
Win4Assert( lcid == xQueryItem->GetLocale() ); //
// Now that we have the appropriate locale information, we can generate
// the output format information.
//
outputFormat->LoadNumberFormatInfo( lcid );
Win4Assert( 0 != wcsLocale.GetPointer() ); variableSet->AcquireStringValue( ISAPI_CI_LOCALE, wcsLocale.GetPointer(), 0 ); wcsLocale.Acquire(); } else { //
// Attempt to find a matching query with the same restriction,
// scope, sort order, etc.
xQueryItem.Set( FindItem( variableSet, outputFormat, wcsIDQFile, securityIdentity ) );
if ( !xQueryItem.IsNull() ) (*_pcTotalQueries)++; }
// make sure the query isn't in bad shape (e.g. the pipe went away)
if ( !xQueryItem.IsNull() ) { TRY { // this will force a GetStatus(), and will throw on problems
xQueryItem->IsQueryDone(); } CATCH( CException, e ) { // zombify the query before releasing it, so it isn't pulled
// out of the cache and used again.
xQueryItem->Zombify(); Release( xQueryItem.Acquire(), FALSE ); } END_CATCH; }
if ( xQueryItem.IsNull() ) { //
// We still haven't found a matching query item. Build a new one
//
xQueryItem.Set( CreateNewQuery( wcsIDQFile, variableSet, outputFormat, securityIdentity, fAsynchronous ) );
ciGibDebugOut(( DEB_ITRACE, "Item NOT found in cache\n" ));
(*_pcTotalQueries)++; (*_pcCacheMisses)++; (*_pcCacheHitsAndMisses)++; } else { (*_pcCacheHits)++; (*_pcCacheHitsAndMisses)++; fAsynchronous = FALSE; ciGibDebugOut(( DEB_ITRACE, "Item found in cache\n" ));
SetupDefaultCiVariables( variableSet.GetReference() ); SetupDefaultISAPIVariables( variableSet.GetReference() ); }
Win4Assert( ( fAsynchronous && xQueryItem.IsNull() ) || ( !fAsynchronous && !xQueryItem.IsNull() ) );
// If it's asynchronous, CreateNewQuery will have already bumped the
// count of running queries under lock, so we won't have the problem
// where the query will be completed, output, and released before this
// increment is called.
if ( !fAsynchronous ) IncrementRunningQueries();
return xQueryItem.Acquire(); } //CreateOrFindQuery
//+---------------------------------------------------------------------------
//
// Member: CWQueryCache::FindItem - private
//
// Synopsis: Locates the query item specified by Bookmark. The
// bookmark is used to generate the address of the CWQueryItem
// and the lSkipCount is used when the query is using a sequential
// cursor to determine if the rowset is positioned at the
// appropriate row.
//
// Arguments: [bookmark] - bookmark of the item to find
// [lSkipCount] - number of records to skip past bookmark
// [securityIdentity] - security LUID of the browser
//
// Returns: query item matching [bookmark]; 0 otherwise
//
// History: 96-Jan-18 DwightKr Created
//
//----------------------------------------------------------------------------
CWQueryItem * CWQueryCache::FindItem( CWQueryBookmark & bookmark, LONG lSkipCount, CSecurityIdentity securityIdentity ) { if ( 0 == *_pcCacheItems ) return 0;
CWQueryItem * pQueryItem = 0;
// ==========================================
CLock lock( _mutex );
TRY { CWQueryItem * pItem = (CWQueryItem *) bookmark.GetQueryItem();
//
// Iterate through the cache looking for this item.
//
for ( CWQueryCacheForwardIter iter(*this); !AtEnd(iter); Advance(iter) ) { if ( iter.Get() == pItem ) { //
// Check to verify that all of the following match:
//
// 1 If sequential, its not in use
// 2. The sequence number matches
// 3. The memory block addressed is a CWQueryItem; check signature
// 4. We have the same security context
// 5. The cached data is still valid
// 6. next records # (for sequential queries only) matches
// 7. the item isn't a zombie
//
if ( (pItem->GetSequenceNumber() == bookmark.GetSequenceNumber()) && ( pItem->GetSignature() == CWQueryItemSignature ) && ( !pItem->IsSequential() || ( (bookmark.GetRecordNumber() + lSkipCount == pItem->GetNextRecordNumber() ) && (pItem->LokGetRefCount() == 0 ) ) ) && ( pItem->LokIsCachedDataValid() ) && ( !pItem->IsZombie() ) && ( securityIdentity.IsEqual(pItem->GetSecurityIdentity()) ) ) { pItem->AddRef(); LokMoveToFront( pItem );
pQueryItem = pItem;
break; } } } } CATCH (CException, e) { } END_CATCH
return pQueryItem;
// ==========================================
}
//+---------------------------------------------------------------------------
//
// Member: CWQueryCache::FindItem - private
//
// Synopsis: Locates the query item specified in the variables
//
// Arguments: [variableSet] - parameters describing the query to find
// [outputFormat] - format of #'s & dates
// [wcsIDQFile] - name of the IDQ file containing the query
// [securityIdentity] - security LUID of the browser
//
// Returns: query item matching; 0 otherwise
//
// History: 96-Jan-18 DwightKr Created
// 96-Mar-13 DwightKr Allow output columns to be replaceable
// 97-Jun-11 KyleP Use web server from output format
//
//----------------------------------------------------------------------------
CWQueryItem * CWQueryCache::FindItem( XPtr<CVariableSet> & variableSet, XPtr<COutputFormat> & outputFormat, WCHAR const * wcsIDQFile, CSecurityIdentity securityIdentity ) { // The idq search and parameter replacement need to be done
// regardless of whether a cached query will be used.
CIDQFile * pIdqFile = _idqFileList.Find( wcsIDQFile, outputFormat->CodePage(), securityIdentity ); XInterface<CIDQFile> xIDQFile( pIdqFile );
//
// Get string values for all parameters that define the query
//
ULONG cwc; XPtrST<WCHAR> wcsRestriction( ReplaceParameters( pIdqFile->GetRestriction(), variableSet.GetReference(), outputFormat.GetReference(), cwc ) );
XPtrST<WCHAR> wcsScope( ReplaceParameters( pIdqFile->GetScope(), variableSet.GetReference(), outputFormat.GetReference(), cwc ) );
// ConvertSlashToBackSlash( wcsScope.GetPointer() );
XPtrST<WCHAR> wcsSort( ReplaceParameters( pIdqFile->GetSort(), variableSet.GetReference(), outputFormat.GetReference(), cwc ) );
XPtrST<WCHAR> wcsTemplate( ReplaceParameters( pIdqFile->GetHTXFileName(), variableSet.GetReference(), outputFormat.GetReference(), cwc ) );
XPtrST<WCHAR> wcsCatalog( ReplaceParameters( pIdqFile->GetCatalog(), variableSet.GetReference(), outputFormat.GetReference(), cwc ) );
XPtrST<WCHAR> wcsColumns( ReplaceParameters( pIdqFile->GetColumns(), variableSet.GetReference(), outputFormat.GetReference(), cwc ) );
XPtrST<WCHAR> wcsCiFlags( ReplaceParameters( pIdqFile->GetCiFlags(), variableSet.GetReference(), outputFormat.GetReference(), cwc ) );
XPtrST<WCHAR> wcsForceUseCI( ReplaceParameters( pIdqFile->GetForceUseCI(), variableSet.GetReference(), outputFormat.GetReference(), cwc ) );
XPtrST<WCHAR> wcsDeferTrimming( ReplaceParameters( pIdqFile->GetDeferTrimming(), variableSet.GetReference(), outputFormat.GetReference(), cwc ) );
LONG lMaxRecordsInResultSet = ReplaceNumericParameter( pIdqFile->GetMaxRecordsInResultSet(), variableSet.GetReference(), outputFormat.GetReference(), TheIDQRegParams.GetMaxISRowsInResultSet(), IS_MAX_ROWS_IN_RESULT_MIN, IS_MAX_ROWS_IN_RESULT_MAX );
LONG lFirstRowsInResultSet = ReplaceNumericParameter( pIdqFile->GetFirstRowsInResultSet(), variableSet.GetReference(), outputFormat.GetReference(), TheIDQRegParams.GetISFirstRowsInResultSet(), IS_FIRST_ROWS_IN_RESULT_MIN, IS_FIRST_ROWS_IN_RESULT_MAX );
XArray<WCHAR> wcsLocale; LCID lcid = GetQueryLocale( pIdqFile->GetLocale(), variableSet.GetReference(), outputFormat.GetReference(), wcsLocale );
//
// Now that we have the appropriate locale information, we can generate
// the output format information.
//
outputFormat->LoadNumberFormatInfo( lcid );
Win4Assert( 0 != wcsLocale.GetPointer() ); variableSet->AcquireStringValue( ISAPI_CI_LOCALE, wcsLocale.GetPointer(), 0 ); wcsLocale.Acquire();
XInterface<CWQueryItem> xMatchItem;
// ======================================
if ( 0 != *_pcCacheItems ) { CLock lock( _mutex );
for ( CWQueryCacheForwardIter iter(*this); !AtEnd(iter); Advance(iter) ) { CWQueryItem * pItem = iter.Get(); Win4Assert( pItem != 0 );
//
// Two queries are identical if all of the following match:
//
// 1. idq file names & its not a zombie
// 2. restriction
// 3. scope
// 4. sort set
// 5. template file
// 6. output columns
// 7. security context
// 8. CiFlags
// 9. ForceUseCi
// 10. CiDeferNonIndexedTrimming
// 11. MaxRecordsInResultSet matches
// 12. FirstRowsInResultSet matches
// 13. CiLocale
// 14. next records # (for sequential queries only)
// 15. cached data is still valid
//
//
// Verify condition #1.
//
if ( ( _wcsicmp( wcsIDQFile, pItem->GetIDQFileName() ) != 0 ) && ( !pItem->IsZombie() ) ) { continue; }
ciGibDebugOut(( DEB_ITRACE, "Checking cache item: %x\n", pItem ));
LONG lFirstSequentialRecord = 0; // Assume no first record #
ULONG cMatchedItems = 0; // # of Matched items
//
// Iterate through the list of parameters the browser passed
// to verify conditions #2 - #5 mentioned above. Stop testing
// paramaters as soon as a mismatch is found. Also, save the
// BOOKMARK & SKIPCOUNT so that condition #7 can be verified later.
//
if ( (wcsRestriction.GetPointer() != 0) && (_wcsicmp( wcsRestriction.GetPointer(), pItem->GetRestriction() ) != 0 ) ) { ciGibDebugOut(( DEB_ITRACE, "Searching cache: restrictions DONT match: %ws != %ws\n", wcsRestriction.GetPointer(), pItem->GetRestriction() ));
continue; }
cMatchedItems++; ciGibDebugOut(( DEB_ITRACE, "Searching cache: restrictions match\n" ));
#if DBG == 1
#if 0 // NTBUG #114206 - we don't do this anymore
//
// If we have a scope, verify that there are no slashes
//
WCHAR const * wcsSlashTest = wcsScope.GetPointer(); if ( 0 != wcsSlashTest ) { while ( 0 != *wcsSlashTest ) { Win4Assert( L'/' != *wcsSlashTest ); wcsSlashTest++; } } #endif // 0
#if 0 // bogus assert due to the slash-flipping browser bug
wcsSlashTest = pItem->GetScope(); if ( 0 != wcsSlashTest ) { while ( 0 != *wcsSlashTest ) { Win4Assert( L'/' != *wcsSlashTest ); wcsSlashTest++; } } #endif // 0
#endif // DBG == 1
if ( (wcsScope.GetPointer() != 0) && (_wcsicmp( wcsScope.GetPointer(), pItem->GetScope() ) != 0 ) ) { ciGibDebugOut(( DEB_ITRACE, "Searching cache: scopes DONT match: %ws != %ws\n", wcsScope.GetPointer(), pItem->GetScope() ));
continue; }
cMatchedItems++; ciGibDebugOut(( DEB_ITRACE, "Searching cache: scopes match\n" ));
if ( (wcsSort.GetPointer() != 0) && (pItem->GetSort() != 0 ) && (_wcsicmp( wcsSort.GetPointer(), pItem->GetSort() ) != 0 ) ) { ciGibDebugOut(( DEB_ITRACE, "Searching cache: sorts DONT match: %ws != %ws\n", wcsSort.GetPointer(), pItem->GetSort() ));
continue; }
cMatchedItems++; ciGibDebugOut(( DEB_ITRACE, "Searching cache: sorts match\n" ));
if ( (wcsTemplate.GetPointer() != 0) && (_wcsicmp( wcsTemplate.GetPointer(), pItem->GetTemplate() ) != 0 ) ) { ciGibDebugOut(( DEB_ITRACE, "Searching cache: templates DONT match: %ws != %ws\n", wcsTemplate.GetPointer(), pItem->GetTemplate() ));
continue; }
cMatchedItems++; ciGibDebugOut(( DEB_ITRACE, "Searching cache: templates match\n" ));
if ( (wcsCatalog.GetPointer() != 0) && (_wcsicmp( wcsCatalog.GetPointer(), pItem->GetCatalog() ) != 0 ) ) { ciGibDebugOut(( DEB_ITRACE, "Searching cache: catalogs DONT match: %ws != %ws\n", wcsCatalog.GetPointer(), pItem->GetCatalog() ));
continue; }
cMatchedItems++; ciGibDebugOut(( DEB_ITRACE, "Searching cache: catalogs match\n" ));
if ( (wcsColumns.GetPointer() != 0) && (_wcsicmp( wcsColumns.GetPointer(), pItem->GetColumns() ) != 0 ) ) { ciGibDebugOut(( DEB_ITRACE, "Searching cache: catalogs DONT match: %ws != %ws\n", wcsColumns.GetPointer(), pItem->GetColumns() ));
continue; }
cMatchedItems++; ciGibDebugOut(( DEB_ITRACE, "Searching cache: output columns match\n" ));
if ( (wcsCiFlags.GetPointer() != 0) && (pItem->GetCiFlags() != 0) && (_wcsicmp( wcsCiFlags.GetPointer(), pItem->GetCiFlags() ) != 0 ) ) { ciGibDebugOut(( DEB_ITRACE, "Searching cache: CIFlags DONT match: %ws != %ws\n", wcsCiFlags.GetPointer(), pItem->GetCiFlags() ));
continue; }
cMatchedItems++; ciGibDebugOut(( DEB_ITRACE, "Searching cache: CiFlags columns match\n" ));
if ( (wcsForceUseCI.GetPointer() != 0) && (pItem->GetForceUseCI() != 0) && (_wcsicmp( wcsForceUseCI.GetPointer(), pItem->GetForceUseCI() ) != 0 ) ) { ciGibDebugOut(( DEB_ITRACE, "Searching cache: ForceUseCI DONT match: %ws != %ws\n", wcsForceUseCI.GetPointer(), pItem->GetForceUseCI() ));
continue; }
cMatchedItems++; ciGibDebugOut(( DEB_ITRACE, "Searching cache: ForceUseCI match\n" ));
if ( (wcsDeferTrimming.GetPointer() != 0) && (pItem->GetDeferTrimming() != 0) && (_wcsicmp( wcsDeferTrimming.GetPointer(), pItem->GetDeferTrimming() ) != 0 ) ) { ciGibDebugOut(( DEB_ITRACE, "Searching cache: CiDeferNonIndexedTrimming DONT match: %ws != %ws\n", wcsDeferTrimming.GetPointer(), pItem->GetDeferTrimming() ));
continue; }
cMatchedItems++; ciGibDebugOut(( DEB_ITRACE, "Searching cache: CiDeferNonIndexedTrimming match\n" ));
if ( lMaxRecordsInResultSet != pItem->GetMaxRecordsInResultSet() ) { ciGibDebugOut(( DEB_ITRACE, "Searching cache: MaxRecordsInResultSet DONT match: %d != %d\n", lMaxRecordsInResultSet, pItem->GetMaxRecordsInResultSet() )); continue; }
cMatchedItems++; ciGibDebugOut(( DEB_ITRACE, "Searching cache: MaxRecordsInResultSet match\n" ));
if ( lFirstRowsInResultSet != pItem->GetFirstRowsInResultSet() ) { ciGibDebugOut(( DEB_ITRACE, "Searching cache: FirstRowsInResultSet DONT match: %d != %d\n", lFirstRowsInResultSet, pItem->GetFirstRowsInResultSet() )); continue; }
cMatchedItems++; ciGibDebugOut(( DEB_ITRACE, "Searching cache: FirstRowsInResultSet match\n" ));
if ( lcid != pItem->GetLocale() ) { ciGibDebugOut(( DEB_ITRACE, "Searching cache: lcid DONT match: 0x%x != 0x%x\n", lcid, pItem->GetLocale() ));
continue; }
cMatchedItems++; ciGibDebugOut(( DEB_ITRACE, "Searching cache: lcid match\n" ));
if ( pItem->IsSequential() ) { if ( pItem->LokGetRefCount() != 0 ) { ciGibDebugOut(( DEB_ITRACE, "Searching cache: Sequential query in use\n" )); continue; }
ULONG cwcValue; ULONG ulHash = ISAPIVariableNameHash( ISAPI_CI_BOOKMARK ); WCHAR const * wcsBookmark = variableSet->GetStringValueRAW( ISAPI_CI_BOOKMARK, ulHash, outputFormat.GetReference(), cwcValue ); if ( 0 != wcsBookmark ) { CWQueryBookmark bookMark( wcsBookmark ); lFirstSequentialRecord += bookMark.GetRecordNumber(); }
ulHash = ISAPIVariableNameHash( ISAPI_CI_BOOKMARK_SKIP_COUNT ); WCHAR const * wcsBookmarkSkipCount = variableSet->GetStringValueRAW( ISAPI_CI_BOOKMARK_SKIP_COUNT, ulHash, outputFormat.GetReference(), cwcValue ); if ( 0 != wcsBookmarkSkipCount ) { lFirstSequentialRecord += IDQ_wtol( wcsBookmarkSkipCount ); } }
//
// We've found a match after examining all of the parameters
// passed from the browser. Now verify conditions #6 - #8 above.
//
ciGibDebugOut(( DEB_ITRACE, "Matched %d out of %d parameters\n", cMatchedItems, pItem->GetReplaceableParameterCount() ));
if ( pItem->GetReplaceableParameterCount() > cMatchedItems ) { continue; }
if ( securityIdentity.IsEqual( pItem->GetSecurityIdentity() ) ) { if ( pItem->IsSequential() && ( lFirstSequentialRecord != pItem->GetNextRecordNumber() ) ) { continue; }
if ( !pItem->LokIsCachedDataValid() ) { continue; }
//
// We've found a match. Move it to the front of the list and
// increment its refcount. We assume that a query once referenced
// will be referenced again, hence the move to the front of the
// list.
//
pItem->AddRef(); LokMoveToFront( pItem );
xMatchItem.Set( pItem ); break; } } } // ======================================
//
// If we got a match, then save away the parameters we've expanded. They
// will be used later as output parameters.
//
// Setting ISAPI_CI_MAX_RECORDS_IN_RESULTSET can fail, so we'd
// leak an addref on the query item without the smart pointer.
if ( !xMatchItem.IsNull() ) { if ( 0 != wcsRestriction.GetPointer() ) { variableSet->AcquireStringValue( ISAPI_CI_RESTRICTION, wcsRestriction.GetPointer(), 0 ); wcsRestriction.Acquire(); }
if ( 0 != wcsScope.GetPointer() ) { variableSet->AcquireStringValue( ISAPI_CI_SCOPE, wcsScope.GetPointer(), 0 ); wcsScope.Acquire(); }
if ( 0 != wcsSort.GetPointer() ) { variableSet->AcquireStringValue( ISAPI_CI_SORT, wcsSort.GetPointer(), 0 ); wcsSort.Acquire(); }
if ( 0 != wcsTemplate.GetPointer() ) { variableSet->AcquireStringValue( ISAPI_CI_TEMPLATE, wcsTemplate.GetPointer(), 0 ); wcsTemplate.Acquire(); }
if ( 0 != wcsCatalog.GetPointer() ) { variableSet->AcquireStringValue( ISAPI_CI_CATALOG, wcsCatalog.GetPointer(), 0 ); wcsCatalog.Acquire(); }
if ( 0 != wcsColumns.GetPointer() ) { variableSet->AcquireStringValue( ISAPI_CI_COLUMNS, wcsColumns.GetPointer(), 0 ); wcsColumns.Acquire(); }
if ( 0 != wcsCiFlags.GetPointer() ) { variableSet->AcquireStringValue( ISAPI_CI_FLAGS, wcsCiFlags.GetPointer(), 0 ); wcsCiFlags.Acquire(); }
if ( 0 != wcsForceUseCI.GetPointer() ) { variableSet->AcquireStringValue( ISAPI_CI_FORCE_USE_CI, wcsForceUseCI.GetPointer(), 0 ); wcsForceUseCI.Acquire(); }
if ( 0 != wcsDeferTrimming.GetPointer() ) { variableSet->AcquireStringValue( ISAPI_CI_DEFER_NONINDEXED_TRIMMING, wcsDeferTrimming.GetPointer(), 0 ); wcsDeferTrimming.Acquire(); }
PROPVARIANT propVariant; propVariant.vt = VT_I4; propVariant.lVal = lMaxRecordsInResultSet; variableSet->SetVariable( ISAPI_CI_MAX_RECORDS_IN_RESULTSET, &propVariant, 0 );
PROPVARIANT propVar; propVar.vt = VT_I4; propVar.lVal = lFirstRowsInResultSet; variableSet->SetVariable( ISAPI_CI_FIRST_ROWS_IN_RESULTSET, &propVar, 0 ); }
return xMatchItem.Acquire(); } //FindItem
//+---------------------------------------------------------------------------
//
// Member: CWQueryCache::DeleteOldQueries - public
//
// Synopsis: If we haven't checked the query list for at least the maximum
// time an unused query is allowed to remain in the list, walk
// the query item list and delete all items whose last access
// time is greater than the purge time.
//
// History: 96-Jan-18 DwightKr Created
//
//----------------------------------------------------------------------------
void CWQueryCache::DeleteOldQueries() { time_t ttNow = time(0); time_t oldestAllowableTime = ttNow - (60 * TheIDQRegParams.GetISCachePurgeInterval());
//
// Don't free the queries under lock. It can take a long long time.
// We don't need an allocation here; delete at most 40 queries.
//
//
// NOTE: the code below can't throw or we'll leak queries!
//
const int cAtMost = 40; CWQueryItem * aItems[ cAtMost ]; int iQueries = 0;
{ CLock lock( _mutex ); CWQueryCacheForwardIter iter( *this );
while ( !AtEnd( iter ) && iQueries < cAtMost ) { //
// If no one is using this query, and it is old, delete it now.
//
CWQueryItem * pItem = iter.Get();
if ( pItem->LokGetRefCount() == 0 && ( pItem->LokGetLastAccessTime() < oldestAllowableTime || pItem->IsZombie() ) ) { Advance( iter ); pItem->Unlink(); aItems[ iQueries++ ] = pItem;
Win4Assert( *_pcCacheItems > 0 ); (*_pcCacheItems)--;
ciGibDebugOut(( DEB_ITRACE, "Removing an expired item from the cache, %d queries cached\n", *_pcCacheItems )); } else { Advance( iter ); } } }
for ( int i = 0; i < iQueries; i++ ) Remove( aItems[ i ] ); } //DeleteOldQueries
//+---------------------------------------------------------------------------
//
// Member: CWQueryCache::LokMoveToFront - public
//
// Arguments: [pItem] - the CWQueryItem to move to the front the of list
//
// Synopsis: Moves a query item to the front of the list. This routine
// is called whenever a query object is accessed.
//
// History: 96-Jan-18 DwightKr Created
// 96-Feb-20 DwightKr Remove time reset
//
//----------------------------------------------------------------------------
void CWQueryCache::LokMoveToFront( CWQueryItem * pItem ) { Win4Assert( pItem != 0 );
pItem->Unlink(); Push(pItem); }
//+---------------------------------------------------------------------------
//
// Member: CWQueryCache::CreateNewQuery - private
//
// Synopsis: Creates a new query and adds it to the linked list of items.
//
// Arguments: [wcsIDQFile] - name of the IDQ file referenced by this query
// [variableSet] - variables used to create this new query
// [outputFormat] - format of numbers & dates
// [securityIdentity] - security context of this query
//
// Returns: a new CWQueryItem, fully constructed with the query results
// already cached & the item added to the linked list of
// cached queries ONLY if this is a non-sequential query.
//
// History: 18-Jan-96 DwightKr Created
// 11-Jun-97 KyleP Use web server from output format
//
//----------------------------------------------------------------------------
CWQueryItem * CWQueryCache::CreateNewQuery( WCHAR const * wcsIDQFile, XPtr<CVariableSet> & variableSet, XPtr<COutputFormat> & outputFormat, CSecurityIdentity securityIdentity, BOOL & fAsynchronous ) { LONG lFirstRecordNumber = 1; CVariable *pVariable = variableSet->Find( ISAPI_CI_FIRST_RECORD_NUMBER );
if ( 0 != pVariable ) { lFirstRecordNumber = IDQ_wtol( pVariable->GetStringValueRAW() ); }
//
// Attempt to find a parsed version of the IDQ file in the IDQ file
// list.
//
CIDQFile * pIdqFile = _idqFileList.Find( wcsIDQFile, outputFormat->CodePage(), securityIdentity );
XInterface<CIDQFile> xIDQFile( pIdqFile );
//
// Did we parse the IDQ file with the correct local & code page? Check
// to see if we used the wrong one. We attempted to open it with the locale
// and code page specified by the browser. Determine if the IDQ file
// overrides this value.
//
XArray<WCHAR> wcsLocale; LCID locale = GetQueryLocale( pIdqFile->GetLocale(), variableSet.GetReference(), outputFormat.GetReference(), wcsLocale );
Win4Assert( !pIdqFile->IsCanonicalOutput() );
if ( outputFormat->GetLCID() != locale ) { ciGibDebugOut(( DEB_ITRACE, "Wrong codePage used for loading IDQ file, used 0x%x retrying with 0x%x\n", outputFormat->CodePage(), LocaleToCodepage(locale) ));
//
// We've parsed the IDQ file with the wrong locale.
//
_idqFileList.Release( *(xIDQFile.Acquire()) ); outputFormat->LoadNumberFormatInfo( locale );
pIdqFile = _idqFileList.Find( wcsIDQFile, outputFormat->CodePage(), securityIdentity ); xIDQFile.Set( pIdqFile ); }
SetupDefaultCiVariables( variableSet.GetReference() ); SetupDefaultISAPIVariables( variableSet.GetReference() );
//
// Determine which output columns this IDQ file uses
//
ULONG cwcOut; XPtrST<WCHAR> wcsColumns( ReplaceParameters( pIdqFile->GetColumns(), variableSet.GetReference(), outputFormat.GetReference(), cwcOut ) );
CDynArray<WCHAR> awcsColumns;
XPtr<CDbColumns> dbColumns( pIdqFile->ParseColumns( wcsColumns.GetPointer(), variableSet.GetReference(), awcsColumns ) );
//
// Attempt to find a parsed version of the HTX file in the HTX file
// list.
//
Win4Assert( !pIdqFile->IsCanonicalOutput() );
CHTXFile & htxFile = _htxFileList.Find( pIdqFile->GetHTXFileName(), variableSet.GetReference(), outputFormat.GetReference(), securityIdentity, outputFormat->GetServerInstance() );
XInterface<CHTXFile> xHTXFile( &htxFile );
CWQueryItem *pNewItem = new CWQueryItem( *pIdqFile, htxFile, wcsColumns, dbColumns, awcsColumns, GetNextSequenceNumber(), lFirstRecordNumber, securityIdentity ); XPtr<CWQueryItem> xNewItem( pNewItem); xIDQFile.Acquire(); xHTXFile.Acquire();
pNewItem->ExecuteQuery( variableSet.GetReference(), outputFormat.GetReference() );
if ( xNewItem->IsSequential() || xNewItem->IsQueryDone() ) { AddToCache( xNewItem.GetPointer() );
fAsynchronous = FALSE;
ciGibDebugOut(( DEB_ITRACE, "Creating a synchronous web query\n" )); } else { { // ==========================================
CLock lock( _mutex );
if ( fTheActiveXSearchShutdown ) THROW( CException(STATUS_TOO_LATE) );
// xNewItem is acquired in CWPendingQueryItem's constructor
CWPendingQueryItem * pItem = new CWPendingQueryItem( xNewItem, outputFormat, variableSet );
// _pendingQueue's array has been pre-allocated to a large size,
// so it can't fail. Even if it could fail we don't want to
// put CWPendingQueryItem in an xptr since the webServer may
// be released twice on failure.
_pendingQueue.Add( pItem, _pendingQueue.Count() );
IncrementRunningQueries(); // ==========================================
}
fAsynchronous = TRUE; ciGibDebugOut(( DEB_ITRACE, "Creating an asynchronous web query\n" ));
Wakeup(); // wake up thread to check if the query is completed
}
Win4Assert( ( fAsynchronous && xNewItem.IsNull() ) || ( !fAsynchronous && !xNewItem.IsNull() ) );
return xNewItem.Acquire(); } //CreateNewQuery
//+---------------------------------------------------------------------------
//
// Member: CWQueryCache::AddToCache - public
//
// Arguments: [pNewItem] - Item to add to cache
//
// History: 96-Mar-04 DwightKr Created
//
//----------------------------------------------------------------------------
void CWQueryCache::AddToCache( CWQueryItem * pNewItem ) { // This assert can hit if the user has just lowered
// IsapiMaxEntriesInQueryCache and the cache was full and a new query
// was just issued and the cache has not yet been reduced.
//Win4Assert ( *_pcCacheItems <= TheIDQRegParams.GetMaxISQueryCache() );
//
// If we already have too many query items in the cache, try to
// delete some.
//
CWQueryItem * pItemToDelete = 0;
// ==========================================
if ( pNewItem->CanCache() ) { CLock lock( _mutex );
CWQueryCacheBackwardIter iter(*this); while ( *_pcCacheItems >= TheIDQRegParams.GetMaxISQueryCache() && !AtEnd(iter) ) { ciGibDebugOut(( DEB_ITRACE, "Too many items in cache, attempting to delete one\n" ));
CWQueryItem * pItem = iter.Get();
if ( pItem->LokGetRefCount() == 0 ) { pItemToDelete = pItem; pItemToDelete->Unlink();
Win4Assert( *_pcCacheItems > 0 ); (*_pcCacheItems)--;
break; } else { BackUp(iter); } } }
if ( 0 != pItemToDelete ) { Remove( pItemToDelete ); }
// If we STILL have too many queries in the cache, we couldn't delete
// any because they were all in use.
//
pNewItem->AddRef();
if ( 0 != TheIDQRegParams.GetMaxISQueryCache() ) { // ==========================================
CLock lock( _mutex );
//
// If we're shutting down, don't attempt to put anything in the cache.
//
if ( *_pcCacheItems < TheIDQRegParams.GetMaxISQueryCache() && pNewItem->CanCache() && !fTheActiveXSearchShutdown ) { Push( pNewItem ); pNewItem->InCache();
(*_pcCacheItems)++; } else { ciGibDebugOut(( DEB_ITRACE, "Still too many items in cache, creating non-cached query\n" )); } // ==========================================
} }
//+---------------------------------------------------------------------------
//
// Member: CWQueryCache::Remove - public
//
// Arguments: [pItem] - Item to remove from cache
//
// Synopsis: Removes from cache and releases IDQ & HTX files.
//
// History: 96-Mar-28 DwightKr Created
//
//----------------------------------------------------------------------------
void CWQueryCache::Remove( CWQueryItem * pItem ) { Win4Assert( 0 != pItem );
delete pItem; } //Remove
//+---------------------------------------------------------------------------
//
// Member: CWQueryCache::Release - public
//
// Arguments: [pItem] -- Item to release - return to cache
// [fDecRunningQueries] -- If true, the count of running
// queries should be decremented.
//
// Synopsis: Decrements the refcount, and attempts to add it to the
// cache if it's not already there.
//
// History: 96-Jan-18 DwightKr Created
//
//----------------------------------------------------------------------------
void CWQueryCache::Release( CWQueryItem * pItem, BOOL fDecRunningQueries ) { if ( 0 != pItem ) { pItem->Release();
if ( fDecRunningQueries ) DecrementRunningQueries();
//
// The item may not be in the cache, because the cache was full
// at the time the query was created.
//
if ( ! pItem->IsInCache() ) { //
// Don't attempt to add the query item to the cache here. If
// the add operation throws, we won't release the refcount
// on the idq & htx files.
//
Remove( pItem ); } } } //Release
//+---------------------------------------------------------------------------
//
// Member: CWQueryCache::FlushCache - public
//
// Synopsis: Flushes the cache .. waits until the cache is empty
//
// History: 96-Jan-18 DwightKr Created
//
//----------------------------------------------------------------------------
void CWQueryCache::FlushCache() { //
// Delete each of the pending asynchronous queries. Take the lock so
// that the worker thread can't wake up and start processing one of
// these items while we're deleting it.
//
{ // ==========================================
CLock shutdownLock( _mutexShutdown ); CLock lock( _mutex ); for ( unsigned i=0; i<_pendingQueue.Count(); i++ ) { delete _pendingQueue.Get(i); }
_pendingQueue.Clear(); Win4Assert( _pendingQueue.Count() == 0 );
// ==========================================
}
//
// Wait for each of the cached queries to be deleted. We many have to
// sleep for a bit to allow a thread to write the results of an
// active query.
//
while ( *_pcCacheItems > 0 ) { ciGibDebugOut(( DEB_ITRACE, "Flushing the cache\n" ));
{ // ==========================================
CLock lock( _mutex );
CWQueryCacheForwardIter iter(*this);
while ( !AtEnd(iter) ) { CWQueryItem * pItem = iter.Get();
if ( pItem->LokGetRefCount() == 0 ) { Advance(iter);
pItem->Unlink();
Remove( pItem );
Win4Assert( *_pcCacheItems > 0 ); (*_pcCacheItems)--; } else { Advance(iter); } }
// ==========================================
}
//
// If there are more items to delete, release the lock, wait a
// bit then try again.
//
if ( *_pcCacheItems > 0 ) { ciGibDebugOut(( DEB_ITRACE, "CWQueryCache::FlushCache waiting for queries to complete\n" )); Sleep(1000); } } }
//+---------------------------------------------------------------------------
//----------------------------------------------------------------------------
void SetupDefaultCiVariables( CVariableSet & variableSet ) { //
// Setup the default Ci variables
//
for ( unsigned i=0; i<cCiGlobalVars; i++) { if ( variableSet.Find( aCiGlobalVars[i].wcsVariableName ) == 0 ) { PROPVARIANT Variant; Variant.vt = aCiGlobalVars[i].vt; Variant.uhVal.QuadPart = aCiGlobalVars[i].i64DefaultValue;
variableSet.SetVariable( aCiGlobalVars[i].wcsVariableName, &Variant, aCiGlobalVars[i].flags); } } }
//+---------------------------------------------------------------------------
//----------------------------------------------------------------------------
void SetupDefaultISAPIVariables( CVariableSet & variableSet ) { //
// Setup the default ISAPI variables
//
for ( unsigned i=0; i<cISAPIGlobalVars; i++) { if ( variableSet.Find( aISAPIGlobalVars[i].wcsVariableName ) == 0 ) { PROPVARIANT Variant; Variant.vt = aISAPIGlobalVars[i].vt; Variant.uhVal.QuadPart = aISAPIGlobalVars[i].i64DefaultValue;
variableSet.SetVariable( aISAPIGlobalVars[i].wcsVariableName, &Variant, aISAPIGlobalVars[i].flags); } } }
#if (DBG == 1)
//+---------------------------------------------------------------------------
//
// Member: CWQueryCache::Dump - public
//
// Arguments: [string] - buffer to send results to
// [variableSet] - replaceable parameters
// [outputFormat] - format of numbers & dates
//
// Synopsis: Dumps the state of all queries
//
// Notes: The variableSet and outputFormat are for the
// current request, i.e., the !dump command, not
// for the individual queries in the cache.
//
// History: 96-Jan-18 DwightKr Created
//
//----------------------------------------------------------------------------
WCHAR wcsDumpBuffer[500];
void CWQueryCache::Dump( CVirtualString & string, CVariableSet & variableSet, COutputFormat & outputFormat ) { // ==========================================
CLock lock( _mutex );
string.StrCat( L"<H1>Dump of query cache</H1><P>" // L"<A HREF=\"#stats\">Cache statistics</A><BR>"
// L"<A HREF=\"#pending\">Pending queries</A><BR>"
// L"<A HREF=\"#cache\">Cached queries</A><BR>"
L"<P><H2><A NAME=stats>Cache statistics</H2><P>\n" );
ULONG cwcDumpBuffer = swprintf( wcsDumpBuffer, L"Unique queries since service startup: %d<BR>" L"Number of items in cache: %d<BR>" L"Number of cache hits: %d<BR>" L"Number of cache misses: %d<BR>" L"Number of cache hits and misses: %d<BR>" L"Number of running queries: %d<BR>" L"Number of queries run thus far: %d<BR>\n", _ulSequenceNumber, *_pcCacheItems, *_pcCacheHits, *_pcCacheMisses, *_pcCacheHitsAndMisses, *_pcRunningQueries, *_pcTotalQueries );
string.StrCat( wcsDumpBuffer, cwcDumpBuffer );
string.StrCat( L"<H2><A NAME=pending>Pending Queries</H2><P>" ); //
// Dump the pending query queue
//
for (unsigned i=0; i<_pendingQueue.Count(); i++ ) { if ( _pendingQueue[i] ) { cwcDumpBuffer = swprintf( wcsDumpBuffer, L"<P>\n<H3>Pending query # %d contents:</H3><P>\n", i+1 );
string.StrCat( wcsDumpBuffer, cwcDumpBuffer );
_pendingQueue[i]->LokDump( string /*, variableSet, outputFormat */); } }
string.StrCat( L"<H2><HREF NAME=cache>Cached Queries</H2><P>" ); i = 1; for ( CWQueryCacheForwardIter iter(*this); !AtEnd(iter); Advance(iter), i++ ) { cwcDumpBuffer = swprintf( wcsDumpBuffer, L"<P>\n<H3>Cached query # %d contents:</H3><P>\n", i );
string.StrCat( wcsDumpBuffer, cwcDumpBuffer );
iter.Get()->LokDump( string /*, variableSet, outputFormat */); }
// ==========================================
}
//+---------------------------------------------------------------------------
//
// Member: CWQueryCache::Internal - public
//
// Arguments: [variableSet] - variable containing command to execute
// [outputFormat] - format of numbers & dates
// [string] - buffer to send results to
//
// Synopsis: Executes one of a number of internal commands to the
// query cache.
//
// History: 96-Jan-18 DwightKr Created
// 96-Fen-21 DwightKr Added help
//
//----------------------------------------------------------------------------
BOOL CWQueryCache::Internal( CVariableSet & variableSet, COutputFormat & outputFormat, CVirtualString & string ) { CVariable * pVarRestriction = variableSet.Find(ISAPI_CI_RESTRICTION );
if ( (0 != pVarRestriction) && (0 != pVarRestriction->GetStringValueRAW()) ) { if (_wcsicmp( pVarRestriction->GetStringValueRAW(), L"!dump") == 0 )
{ string.StrCat( L"<HEAD><TITLE>Dump of query cache</TITLE></HEADER>" ); Dump( string, variableSet, outputFormat ); return TRUE; } else if (_wcsicmp( pVarRestriction->GetStringValueRAW(), L"!flush") == 0 ) { string.StrCat( L"<HEAD><TITLE>Cache flush</TITLE></HEADER>Flushing cache...<BR>\n" );
FlushCache();
string.StrCat( L"Flush complete<BR>\n" ); return TRUE; } else if (_wcsicmp( pVarRestriction->GetStringValueRAW(), L"!?") == 0 ) { string.StrCat( L"<HEAD><TITLE>Help</TITLE></HEADER>" ); string.StrCat( L"Avaiable commands:<BR>\n"); string.StrCat( L"!dump - dumps contents of the query cache<BR>\n" ); string.StrCat( L"!flush - empties the query cache<BR>\n" ); string.StrCat( L"!? - help (this page)<BR>\n" ); return TRUE; } }
return FALSE; } #endif // DBG == 1
//+---------------------------------------------------------------------------
//
// Member: CWQueryCache::AddToPendingRequestQueue - public
//
// Synopsis: Adds the ECB to the pending queue, if we're not shutting down
//
// History: 96-May-22 DwightKr Created
//
//----------------------------------------------------------------------------
BOOL CWQueryCache::AddToPendingRequestQueue( EXTENSION_CONTROL_BLOCK *pEcb ) { // Don't take the query cache lock here -- there is no reason to since
// reads are atomic and we don't want IIS to make a zillion threads.
if ( fTheActiveXSearchShutdown || TheWebPendingRequestQueue.IsFull( ) ) { return FALSE; }
TOKEN_STATISTICS TokenInformation; HANDLE hToken = GetSecurityToken(TokenInformation);
//
// It must be an impersonation token, hence we must have a valid handle.
// Build a pending request using the ECB and the security token,
//
Win4Assert( INVALID_HANDLE_VALUE != hToken ); Win4Assert( TokenInformation.TokenType == TokenImpersonation );
CWebPendingItem item( pEcb, hToken );
//
// Add the request to the pending queue.
//
return TheWebPendingRequestQueue.Add( item ); }
//+---------------------------------------------------------------------------
//
// Member: CWQueryCache::Shutdown - public
//
// Synopsis: Stops and empties the query cache
//
// History: 96-May-22 DwightKr Created
//
//----------------------------------------------------------------------------
void CWQueryCache::Shutdown() { //
// First set the shutdown flag so that no queues will be added to after
// this point.
//
{ CLock lock( _mutex ); fTheActiveXSearchShutdown = TRUE; }
FlushCache();
Wakeup(); // wake up thread & wait for death
WaitForSingleObject( _threadWatchDog.GetHandle(), INFINITE );
Win4Assert( IsEmpty() && "Query cache must be empty after flush" ); }
//+---------------------------------------------------------------------------
//
// Member: CICommandCache::CICommandCache, public
//
// Synopsis: Constructor for the ICommand cache
//
// History: 97-Feb-23 dlee Created
//
//----------------------------------------------------------------------------
CICommandCache::CICommandCache() : _ulSig( LONGSIG( 'c', 'i', 'c', 'c' ) ) { //
// These registry params are taken at startup and ignored
// thereafter. This isn't an issue on big machines where the
// query cache is turned off, but we might want to fix it.
// Maybe someday, but IDQ is pretty much a dead technology.
//
unsigned cItems = TheIDQRegParams.GetMaxISQueryCache(); ULONG factor = TheIDQRegParams.GetISRequestThresholdFactor();
SYSTEM_INFO si; GetSystemInfo( &si );
// # of threads allowed in idq + # pending queries + # queries in cache
cItems += ( 2 * si.dwNumberOfProcessors * factor );
_aItems.Init( cItems );
RtlZeroMemory( _aItems.GetPointer(), _aItems.SizeOf() );
const CLSID clsidCommandCreator = CLSID_CISimpleCommandCreator; HRESULT hr = CoCreateInstance( clsidCommandCreator, NULL, CLSCTX_INPROC_SERVER, IID_ISimpleCommandCreator, xCmdCreator.GetQIPointer() );
if ( FAILED( hr ) ) THROW( CException() ); } //CICommandCache
//+---------------------------------------------------------------------------
//
// Member: CICommandCache::Make, public
//
// Synopsis: Returns an ICommand, either from the cache or by making one
//
// Arguments: [ppCommand] -- Where the ICommand is returned
// [depth] -- deep / shallow, etc.
// [pwcMachine] -- The machine
// [pwcCatalog] -- The catalog
// [pwcScope] -- The comma separated list of scopes
//
// History: 97-Feb-23 dlee Created
//
//----------------------------------------------------------------------------
SCODE CICommandCache::Make( ICommand ** ppCommand, DWORD depth, WCHAR const * pwcMachine, WCHAR const * pwcCatalog, WCHAR const * pwcScope ) { *ppCommand = 0;
// first look for an available item in the cache
{ CLock lock( _mutex );
for ( unsigned x = 0; x < _aItems.Count(); x++ ) { CICommandItem & item = _aItems[ x ];
if ( ( !item.xCommand.IsNull() ) && ( !item.fInUse ) && ( depth == item.depth ) && ( !wcscmp( pwcMachine, item.aMachine.Get() ) ) && ( !wcscmp( pwcCatalog, item.aCatalog.Get() ) ) && ( !wcscmp( pwcScope, item.aScope.Get() ) ) ) { ciGibDebugOut(( DEB_ITRACE, "reusing icommand from cache\n" )); item.fInUse = TRUE; *ppCommand = item.xCommand.GetPointer(); Win4Assert( 0 != *ppCommand ); return S_OK; } } }
// not found in the cache -- make the item
ciGibDebugOut(( DEB_ITRACE, "creating icommand\n" ));
XInterface<ICommand> xCommand;
SCODE sc = ParseAndMake( xCommand.GetPPointer(), depth, pwcMachine, pwcCatalog, pwcScope );
if ( FAILED( sc ) ) return sc;
// can we put the item in the cache?
{ CLock lock( _mutex );
for ( unsigned x = 0; x < _aItems.Count(); x++ ) { CICommandItem & item = _aItems[ x ];
if ( item.xCommand.IsNull() ) { // First see if we can add it.
item.aMachine.ReSize( wcslen( pwcMachine ) + 1 ); wcscpy( item.aMachine.Get(), pwcMachine );
item.aCatalog.ReSize( wcslen( pwcCatalog ) + 1 ); wcscpy( item.aCatalog.Get(), pwcCatalog );
item.aScope.ReSize( wcslen( pwcScope ) + 1 ); wcscpy( item.aScope.Get(), pwcScope );
// Now mark it as owned
Win4Assert( !item.fInUse );
item.fInUse = TRUE; item.xCommand.Set( xCommand.GetPointer() ); Win4Assert( 0 != item.xCommand.GetPointer() ); item.depth = depth;
break; } } }
*ppCommand = xCommand.Acquire(); Win4Assert( 0 != *ppCommand );
return S_OK; } //Make
//+---------------------------------------------------------------------------
//
// Member: CICommandCache::Release, public
//
// Synopsis: Releases the ICommand to the cache or to be freed
//
// Arguments: [pCommand] -- The ICommand to release.
//
// History: 97-Feb-23 dlee Created
//
//----------------------------------------------------------------------------
void CICommandCache::Release( ICommand * pCommand ) { { CLock lock( _mutex );
// first see if it can be returned to the cache
for ( unsigned x = 0; x < _aItems.Count(); x++ ) { CICommandItem & item = _aItems[ x ];
if ( item.xCommand.GetPointer() == pCommand ) { Win4Assert( item.fInUse ); ciGibDebugOut(( DEB_ITRACE, "returning icommand to cache\n" )); item.fInUse = FALSE; return; } } }
ciGibDebugOut(( DEB_ITRACE, "icommand not in cache, releasing\n" ));
// not in the cache -- just release it
pCommand->Release(); } //Release
//+---------------------------------------------------------------------------
//
// Member: CICommandCache::Remove, public
//
// Synopsis: Removes the item from the cache, likely because the ICommand
// is stale because cisvc went down.
//
// Arguments: [pCommand] -- The ICommand to release.
//
// History: 97-Feb-23 dlee Created
//
//----------------------------------------------------------------------------
void CICommandCache::Remove( ICommand * pCommand ) { { CLock lock( _mutex );
// first see if it is in the cache
for ( unsigned x = 0; x < _aItems.Count(); x++ ) { CICommandItem & item = _aItems[ x ];
if ( item.xCommand.GetPointer() == pCommand ) { Win4Assert( item.fInUse ); item.xCommand.Acquire(); item.fInUse = FALSE; break; } } }
// not in the cache -- just release it
pCommand->Release(); } //Remove
//+---------------------------------------------------------------------------
//
// Member: CICommandCache::Purge, public
//
// Synopsis: Releases all ICommands not currently in use
//
// Arguments: [pCommand] -- The ICommand to release.
//
// History: 97-Feb-23 dlee Created
//
//----------------------------------------------------------------------------
void CICommandCache::Purge() { CLock lock( _mutex );
// Remove all non-used items from the cache.
for ( unsigned x = 0; x < _aItems.Count(); x++ ) { CICommandItem & item = _aItems[ x ];
if ( ( !item.fInUse ) && ( !item.xCommand.IsNull() ) ) { item.xCommand.Free(); } } } //Purge
//+---------------------------------------------------------------------------
//
// Function: IsAVirtualPath
//
// Synopsis: Determines if the path passed is a virtual or physical path.
// If it's a virtual path, then / are changed to \.
//
// History: 96-Feb-14 DwightKr Created
//
//----------------------------------------------------------------------------
BOOL IsAVirtualPath( WCHAR * wcsPath ) { Win4Assert ( 0 != wcsPath ); if ( 0 == wcsPath[0] ) return TRUE;
if ( ( L':' == wcsPath[1] ) || ( L'\\' == wcsPath[0] ) ) { return FALSE; } else { //
// Flip slashes to backslashes
//
for ( WCHAR *wcsLetter = wcsPath; 0 != *wcsLetter; wcsLetter++ ) { if ( L'/' == *wcsLetter ) *wcsLetter = L'\\'; } }
return TRUE; } //IsAVirtualPath
//+---------------------------------------------------------------------------
//
// Function: ParseScopes
//
// Synopsis: Translates a string like:
// " /foo ,c:\bar , "/a b , /c " , j:\dog "
// into a multisz string like:
// "/foo0c:\bar0/a b , /c 0j:\dog00"
//
// Leading and trailing white space is removed unless the
// path is quoted, in which case you get exactly what you
// asked for even though it may be incorrect.
//
// Arguments: [pwcIn] -- the source string
// [pwcOut] -- the multisz result string, guaranteed to be
// no more than 1 WCHAR larger than pwcIn.
//
// History: 97-Jun-17 dlee Created
//
//----------------------------------------------------------------------------
ULONG ParseScopes( WCHAR const * pwcIn, WCHAR * pwcOut ) { ULONG cScopes = 0;
while ( 0 != *pwcIn ) { // eat space and commas
while ( L' ' == *pwcIn || L',' == *pwcIn ) pwcIn++;
if ( 0 == *pwcIn ) break;
// is this a quoted path?
if ( L'"' == *pwcIn ) { pwcIn++;
while ( 0 != *pwcIn && L'"' != *pwcIn ) *pwcOut++ = *pwcIn++;
if ( L'"' != *pwcIn ) THROW( CIDQException( MSG_CI_IDQ_BAD_SCOPE_OR_CATALOG, 0 ) );
pwcIn++; *pwcOut++ = 0; } else { while ( 0 != *pwcIn && L',' != *pwcIn ) *pwcOut++ = *pwcIn++;
// back up over trailing spaces
while ( L' ' == * (pwcOut - 1) ) pwcOut--;
*pwcOut++ = 0; }
cScopes++; }
if ( 0 == cScopes ) THROW( CIDQException( MSG_CI_IDQ_BAD_SCOPE_OR_CATALOG, 0 ) );
// end the string with a second null
*pwcOut = 0;
return cScopes; } //ParseScopes
//+---------------------------------------------------------------------------
//
// Member: CICommandCache::ParseAndMake, private
//
// Synopsis: Parses parameters for an ICommand and creates one
//
// Arguments: [ppCommand] -- Where the ICommand is returned
// [depth] -- deep / shallow, etc.
// [pwcMachine] -- The machine
// [pwcCatalog] -- The catalog
// [pwcScope] -- The comma separated list of scopes
//
// History: 97-Feb-23 dlee Created
//
//----------------------------------------------------------------------------
SCODE CICommandCache::ParseAndMake( ICommand ** ppCommand, DWORD depth, WCHAR const * pwcMachine, WCHAR const * pwcCatalog, WCHAR const * pwcScope ) { Win4Assert(pwcMachine && pwcCatalog);
#if 0 // This is actually a bogus check. We don't care how long it is.
if ( wcslen( pwcMachine ) > MAX_COMPUTERNAME_LENGTH ) THROW( CIDQException( MSG_CI_IDQ_BAD_SCOPE_OR_CATALOG, 0 ) ); #endif
if ( wcslen( pwcCatalog ) > MAX_PATH ) THROW( CIDQException( MSG_CI_IDQ_BAD_SCOPE_OR_CATALOG, 0 ) );
IUnknown * pIUnknown; XInterface<ICommand> xCmd;
if (0 == xCmdCreator.GetPointer()) return REGDB_E_CLASSNOTREG;
SCODE sc = xCmdCreator->CreateICommand(&pIUnknown, 0); XInterface<IUnknown> xUnk( pIUnknown );
if ( SUCCEEDED (sc) ) { sc = pIUnknown->QueryInterface(IID_ICommand, xCmd.GetQIPointer()); }
if (FAILED(sc)) return sc;
TRY { CDynArrayInPlace<DWORD> aDepths(2); CDynArrayInPlace<WCHAR const *> aScopes(2); CDynArrayInPlace<WCHAR const *> aMachines(2); CDynArrayInPlace<WCHAR const *> aCatalogs(2);
// allocate +2 for two trailing nulls in the multisz string
ULONG cwcScope = 2 + wcslen( pwcScope ); XGrowable<WCHAR> aScope( cwcScope ); ULONG cScopes = ParseScopes( pwcScope, aScope.Get() );
Win4Assert( 0 != cScopes );
if ( cScopes > 1 ) { // Add support for multiple catalogs, and/or machines,
// and/or depths. For now, all scopes share a single
// catalog/machine/depth. (though you can mix virtual and
// physical). Maybe someday, but IDQ is dead moving forward.
WCHAR *pwc = aScope.Get();
for ( ULONG iScope = 0; iScope < cScopes; iScope++ ) { if ( wcslen( pwc ) >= MAX_PATH ) THROW( CIDQException( MSG_CI_IDQ_BAD_SCOPE_OR_CATALOG, 0 ) );
aDepths[iScope] = depth;
if ( IsAVirtualPath( pwc ) ) aDepths[iScope] |= QUERY_VIRTUAL_PATH;
aScopes[iScope] = pwc;
ciGibDebugOut(( DEB_ITRACE, "scope %d: flags 0x%x '%ws'\n", iScope, aDepths[iScope], pwc ));
// pwc is a multi-sz string. Skip to the next scope
pwc += ( 1 + wcslen( pwc ) );
aMachines[iScope] = pwcMachine; aCatalogs[iScope] = pwcCatalog; } } else { aMachines[0] = pwcMachine; aCatalogs[0] = pwcCatalog; aDepths[0] = depth; WCHAR *pwc = aScope.Get(); if ( IsAVirtualPath( pwc ) ) aDepths[0] |= QUERY_VIRTUAL_PATH; aScopes[0] = pwc; }
SetScopeProperties( xCmd.GetPointer(), cScopes, aScopes.GetPointer(), aDepths.GetPointer(), aCatalogs.GetPointer(), aMachines.GetPointer() );
*ppCommand = xCmd.Acquire(); Win4Assert( 0 != *ppCommand ); } CATCH ( CException, e ) { sc = GetOleError(e); } END_CATCH
return sc; } //ParseAndMake
|