windows-nt/Source/XPSP1/NT/inetsrv/query/web/dll/wqcache.cxx
2020-09-26 16:20:57 +08:00

2912 lines
94 KiB
C++

//+---------------------------------------------------------------------------
//
// 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