//+--------------------------------------------------------------------------- // // Microsoft Windows // Copyright (C) Microsoft Corporation, 1996 - 2000. // // File: wqcache.cxx // // Contents: WEB Query cache class // // History: 96/Jan/3 DwightKr Created // //---------------------------------------------------------------------------- #include #pragma hdrstop #include #include 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 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 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 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 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 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 & variableSet, XPtr & outputFormat, CSecurityIdentity securityIdentity, BOOL & fAsynchronous ) { XInterface 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 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 & variableSet, XPtr & 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 xIDQFile( pIdqFile ); // // Get string values for all parameters that define the query // ULONG cwc; XPtrST wcsRestriction( ReplaceParameters( pIdqFile->GetRestriction(), variableSet.GetReference(), outputFormat.GetReference(), cwc ) ); XPtrST wcsScope( ReplaceParameters( pIdqFile->GetScope(), variableSet.GetReference(), outputFormat.GetReference(), cwc ) ); // ConvertSlashToBackSlash( wcsScope.GetPointer() ); XPtrST wcsSort( ReplaceParameters( pIdqFile->GetSort(), variableSet.GetReference(), outputFormat.GetReference(), cwc ) ); XPtrST wcsTemplate( ReplaceParameters( pIdqFile->GetHTXFileName(), variableSet.GetReference(), outputFormat.GetReference(), cwc ) ); XPtrST wcsCatalog( ReplaceParameters( pIdqFile->GetCatalog(), variableSet.GetReference(), outputFormat.GetReference(), cwc ) ); XPtrST wcsColumns( ReplaceParameters( pIdqFile->GetColumns(), variableSet.GetReference(), outputFormat.GetReference(), cwc ) ); XPtrST wcsCiFlags( ReplaceParameters( pIdqFile->GetCiFlags(), variableSet.GetReference(), outputFormat.GetReference(), cwc ) ); XPtrST wcsForceUseCI( ReplaceParameters( pIdqFile->GetForceUseCI(), variableSet.GetReference(), outputFormat.GetReference(), cwc ) ); XPtrST 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 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 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 & variableSet, XPtr & 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 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 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 wcsColumns( ReplaceParameters( pIdqFile->GetColumns(), variableSet.GetReference(), outputFormat.GetReference(), cwcOut ) ); CDynArray awcsColumns; XPtr 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 xHTXFile( &htxFile ); CWQueryItem *pNewItem = new CWQueryItem( *pIdqFile, htxFile, wcsColumns, dbColumns, awcsColumns, GetNextSequenceNumber(), lFirstRecordNumber, securityIdentity ); XPtr 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; iDump of query cache

" // L"Cache statistics
" // L"Pending queries
" // L"Cached queries
" L"

Cache statistics

\n" ); ULONG cwcDumpBuffer = swprintf( wcsDumpBuffer, L"Unique queries since service startup: %d
" L"Number of items in cache: %d
" L"Number of cache hits: %d
" L"Number of cache misses: %d
" L"Number of cache hits and misses: %d
" L"Number of running queries: %d
" L"Number of queries run thus far: %d
\n", _ulSequenceNumber, *_pcCacheItems, *_pcCacheHits, *_pcCacheMisses, *_pcCacheHitsAndMisses, *_pcRunningQueries, *_pcTotalQueries ); string.StrCat( wcsDumpBuffer, cwcDumpBuffer ); string.StrCat( L"

Pending Queries

" ); // // Dump the pending query queue // for (unsigned i=0; i<_pendingQueue.Count(); i++ ) { if ( _pendingQueue[i] ) { cwcDumpBuffer = swprintf( wcsDumpBuffer, L"

\n

Pending query # %d contents:

\n", i+1 ); string.StrCat( wcsDumpBuffer, cwcDumpBuffer ); _pendingQueue[i]->LokDump( string /*, variableSet, outputFormat */); } } string.StrCat( L"

Cached Queries

" ); i = 1; for ( CWQueryCacheForwardIter iter(*this); !AtEnd(iter); Advance(iter), i++ ) { cwcDumpBuffer = swprintf( wcsDumpBuffer, L"

\n

Cached query # %d contents:

\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"Dump of query cache" ); Dump( string, variableSet, outputFormat ); return TRUE; } else if (_wcsicmp( pVarRestriction->GetStringValueRAW(), L"!flush") == 0 ) { string.StrCat( L"Cache flushFlushing cache...
\n" ); FlushCache(); string.StrCat( L"Flush complete
\n" ); return TRUE; } else if (_wcsicmp( pVarRestriction->GetStringValueRAW(), L"!?") == 0 ) { string.StrCat( L"Help" ); string.StrCat( L"Avaiable commands:
\n"); string.StrCat( L"!dump - dumps contents of the query cache
\n" ); string.StrCat( L"!flush - empties the query cache
\n" ); string.StrCat( L"!? - help (this page)
\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 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 xCmd; if (0 == xCmdCreator.GetPointer()) return REGDB_E_CLASSNOTREG; SCODE sc = xCmdCreator->CreateICommand(&pIUnknown, 0); XInterface xUnk( pIUnknown ); if ( SUCCEEDED (sc) ) { sc = pIUnknown->QueryInterface(IID_ICommand, xCmd.GetQIPointer()); } if (FAILED(sc)) return sc; TRY { CDynArrayInPlace aDepths(2); CDynArrayInPlace aScopes(2); CDynArrayInPlace aMachines(2); CDynArrayInPlace aCatalogs(2); // allocate +2 for two trailing nulls in the multisz string ULONG cwcScope = 2 + wcslen( pwcScope ); XGrowable 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