/*++ Copyright (c) 1996-1997 Microsoft Corporation Module Name: oxid.cxx Abstract: Object resolver class implementations. COxid, COid, and other auxiliary classes are implemented here. Author: Satish Thatte [SatishT] 03-27-96 --*/ #include // // The CPID class is used for storing process IDs enumerated using the // snapshot generated by CreateToolhelp32Snapshot for use in // the function CheckForCrashedProcessesIfNecessary. // The purpose is to avoid confusing the debug kernel by // calling OpenProcess with a nonexistent process ID // #include class CPID : public CTableElement { private: // declare the members needed for a page static allocator DECL_PAGE_ALLOCATOR public : CIdKey _pid; CPID( DWORD pid ) : _pid(pid) {} virtual DWORD // dummy method in this class Hash() { return _pid.Hash(); } virtual operator ISearchKey&() { return _pid; } DWORD GetPID() { ID id = _pid.Id(); DWORD* pdw = (DWORD*) &id; return *(++pdw); } }; DEFINE_TABLE(CPID) // // COid methods and static members // // define the static members for page-based allocation DEFINE_PAGE_ALLOCATOR(COid) void COid::Rundown() { if (!_pOxid->IsLocal()) { // We don't want to remove it from the gpOidTable at this point // because some other client may come along and want to use // this before it gets dropped from the ping set if the ping fails _pOxid->_pMid->DropClientOid(this); } else { COid *pRemoved = gpOidTable->Remove(*this); ASSERT(pRemoved==this || pRemoved==NULL); } } BOOL COid::OkToRundown() { DWORD dwRefs = References(); ASSERT(dwRefs >= 2); BOOL fLocal = _pOxid->IsLocal(); DWORD dwBaseRefs = fLocal ? 2 : 3; // extra pingset ref if (dwRefs > dwBaseRefs) { return FALSE; } else { // the resolver on the server machine will take care of the delay // in rundown if this is a non-local OID // Check if the time since creation or last release is less // than timeout // Warning: removing the check will fail the middle-man cases // because the middle-man's access time will not be counted. // if (fLocal && ((CTime() - *this) < BaseTimeoutInterval)) { return FALSE; } } return TRUE; } // // COxidInfo methods // ORSTATUS // BUGBUG: perhaps this should have OXID_INFO as the parameter type COxidInfo::Assign( const OXID_INFO& Info ) /*++ Routine Desciption Makes a copy of the incoming info, including the DUALSTRINGARRAY. Arguments: Info - COxidInfo object to be cpied Return Values: OR_OK OR_NOMEM --*/ { _oxidInfo = Info; // all except bindings taken care of return _dsaBindings.Assign(Info.psa,TRUE); // already compressed } // // COxid methods and static members. // // define the static members for page-based allocation DEFINE_PAGE_ALLOCATOR(COxid) COxid::COxid( OXID Oxid, // constructor for remote OXIDs CMid *pMid, USHORT wProtseq, OXID_INFO &OxidInfo ) : _Key(Oxid, pMid->GetMID()), _pProcess(gpPingProcess), _protseq(wProtseq), _fApartment(FALSE), _fRunning(TRUE), _pMid(pMid), _fLocal(FALSE), _info(OxidInfo) { _pMid->Reference(); _optional._remote._dwTimeStamp = 0; } COxid::COxid( // constructor for local OXIDs CProcess *pProcess, OXID_INFO &OxidInfo, BOOL fApartment ) : _Key(AllocateId(), gLocalMID), _pProcess(pProcess), _pMid(gpLocalMid), _protseq(0), _fApartment(fApartment), _fRunning(TRUE), _fLocal(TRUE), _info(OxidInfo) { _optional._local._fRundownThreadStarted = FALSE; _optional._local._hRundownThread = NULL; _optional._local._pfRundownThreadKeepRunning = NULL; _pProcess->Reference(); } COxid::~COxid() { // this works even if executed by nonowner thread StopRundownThreadIfNecessary(); StopRunning(); DUALSTRINGARRAY *pdsaBindings = _info._dsaBindings; OrMemFree(pdsaBindings); if (!IsLocal()) // Don't release the local CMid! { _pMid->Release(); } else // Don't release the PingProcess! { _pProcess->Release(); } } ORSTATUS COxid::OwnOid(COid *pOid) { if (!IsLocal()) { _optional._remote._dwTimeStamp = NULL; } ORSTATUS status = _MyOids.Add(pOid); // acquires a reference if (status == OR_I_DUPLICATE) { status = OR_OK; } return status; } BOOL COxid::HasExpired() { BOOL result = FALSE; ASSERT(!IsLocal()); if (_MyOids.IsEmpty()) { // if we are not running, we are gone if (!_fRunning) { result = TRUE; } else if (_optional._remote._dwTimeStamp == 0) { // Maybe we haven't acquired our first Oid yet // Or someone has been asking for our bindings result = FALSE; } else { // have we been idle long enough? result = (CTime() - CTime(_optional._remote._dwTimeStamp)) >= (BaseTimeoutInterval); } } return result; } COid * COxid::DisownOid(COid *pOid) { // Rundown is idempotent pOid->Rundown(); COid *pMyOid = _MyOids.Remove(*pOid);// releases our reference // The Oid may have been run down and removed already ASSERT(pMyOid == pOid || pMyOid == NULL); // If this is a non-local Oxid and it has no Oids registered // then it becomes a candidate for being eliminated if (!IsLocal() && _MyOids.IsEmpty()) { _optional._remote._dwTimeStamp = GetTickCount(); } return pMyOid; } void COxid::StopRunning() { ReleaseAllOids(); _fRunning = FALSE; } void COxid::ReleaseAllOids() { if (_MyOids.Size()) { COidTableIterator Oids(_MyOids); COid *pOid; while(pOid = Oids.Next()) { DisownOid(pOid); } } } ORSTATUS COxid::GetInfo( OUT OXID_INFO *pInfo ) /*++ Routine Description: Returns the OXID_INFO structure for this oxid for local. Arguments: pInfo - Will contain the standard info, a single _expanded_ string binding and complete security bindings. Return Value: OR_NOMEM - Unable to allocate a parameter. OR_OK - Normally. --*/ { USHORT protseq; PWSTR pwstrT; CDSA dsaBindings; if (!IsRunning()) { return OR_NOSERVER; } if (_fLocal) { dsaBindings.Assign(_pProcess->GetLocalBindings()); protseq = ID_WMSG; } else { protseq = _protseq; // use the one we set when this was created DUALSTRINGARRAY *pdsa = _info._dsaBindings; // auto conversion dsaBindings.Assign(pdsa); // noncopying assignment } if (_pMid->IsLocal()) { pwstrT = FindMatchingProtseq(protseq, dsaBindings->aStringArray); } else { protseq = _pMid->ProtseqOfServer(); PWSTR pwstrAddress = _pMid->AddressOfServer(); if (protseq==0) // there is no working binding { return OR_NOSERVER; } if (pwstrAddress==NULL) // we failed to allocate the string { return OR_NOMEM; } pwstrT = FindMatchingProtseqAndAddr( protseq, pwstrAddress, dsaBindings->aStringArray ); RpcStringFree(&pwstrAddress); } ASSERT(pwstrT != NULL && "OR: Didn't find a matching binding for oxid"); // the memcpy gets everything except the bindings memcpy(pInfo,&_info._oxidInfo,sizeof(_info._oxidInfo)); if (pwstrT) { pInfo->psa = GetStringBinding( pwstrT, dsaBindings->aStringArray + dsaBindings->wSecurityOffset ); } else { // BUGBUG - ronans - can be due to null pwstrT - in this // case OR_NOMEM is not the correct return value. pInfo->psa = NULL; return OR_NOSERVER; } if (0 == pInfo->psa) { return OR_NOMEM; } return(OR_OK); } ORSTATUS COxid::GetRemoteInfo( IN USHORT cClientProtseqs, IN USHORT *aClientProtseqs, IN USHORT cInstalledProtseqs, IN USHORT *aInstalledProtseqs, OUT OXID_INFO *pInfo ) /*++ Routine Description: Returns the OXID_INFO structure for this oxid for remote clients. Arguments: pInfo - Will contain the standard info, a single _expanded_ string binding and complete security bindings. Return Value: OR_NOMEM - Unable to allocate a parameter. OR_OK - Normally. --*/ { PWSTR pwstrT; ORSTATUS status = OR_OK; if (!IsRunning()) { return OR_NOSERVER; } ASSERT(_fLocal); // Do not resolve remote servers for remote clients! DUALSTRINGARRAY * pdsaBindings = _pProcess->GetLocalBindings(); pwstrT = FindMatchingProtseq( cClientProtseqs, aClientProtseqs, pdsaBindings->aStringArray ); if ( pwstrT == NULL ) // try lazy use of protseq(s) { status = _pProcess->UseProtseqIfNeeded( cClientProtseqs, aClientProtseqs, cInstalledProtseqs, aInstalledProtseqs, _info._oxidInfo.dwTid // so we know where to call over WMSG ); pdsaBindings = _pProcess->GetLocalBindings(); } // the memcpy gets everything except the bindings memcpy(pInfo,&_info._oxidInfo,sizeof(_info._oxidInfo)); pInfo->psa = GetMatchingDSA( cClientProtseqs, aClientProtseqs, pdsaBindings ); if (NULL == pInfo->psa) { return OR_NOMEM; } return(status); } ORSTATUS COxid::StartRundownThreadIfNecessary() { DWORD dwThrdId; if (_fApartment || !IsLocal()) // rundown timer is attached to window // or rundown handled by ping server { return OR_OK; } if (_optional._local._fRundownThreadStarted) { ASSERT(_optional._local._hRundownThread); ASSERT(*_optional._local._pfRundownThreadKeepRunning == TRUE); return OR_OK; } SRundownThreadInfo *pInfo = (SRundownThreadInfo*) PrivMemAlloc(sizeof(SRundownThreadInfo)); pInfo->pSelf = this; pInfo->fKeepRunning = TRUE; _optional._local._pfRundownThreadKeepRunning = &(pInfo->fKeepRunning); _optional._local._hRundownThread = CreateThread( NULL, 0, RundownThread, pInfo, 0, &dwThrdId); if (_optional._local._hRundownThread) { _optional._local._fRundownThreadStarted = TRUE; return OR_OK; } else { return GetLastError(); } } ORSTATUS COxid::StopRundownThreadIfNecessary() { if (!IsLocal() || _pProcess->IsCurrentProcess() != TRUE) { return OR_OK; } if (_optional._local._fRundownThreadStarted) { ASSERT(_optional._local._hRundownThread); CloseHandle(_optional._local._hRundownThread); _optional._local._hRundownThread = NULL; _optional._local._fRundownThreadStarted = FALSE; // signal the rundown thread to terminate itself *_optional._local._pfRundownThreadKeepRunning = FALSE; return OR_OK; } else { ASSERT(!_optional._local._hRundownThread); return OR_OK; } } ORSTATUS COxid::StopTimerIfNecessary() // must be called by owner thread { ORSTATUS status = OR_OK; if (_fApartment) { // find the HWND for this thread COleTls tls; OXIDEntry *pOXIDEntry = (OXIDEntry *)tls->pOXIDEntry; HWND hWindow = (HWND) pOXIDEntry->hServerSTA; if (!KillTimer(hWindow,pOXIDEntry->uiTimer)) { status = GetLastError(); } } return status; } void CheckForCrashedProcessesIfNecessary() { CTime CurrentTime; // don't check too often -- note that the subtraction returns a result // denominated in seconds, not in ticks DWORD dwTimeSinceLastCheckInSeconds = CurrentTime - CTime(*gpdwLastCrashedProcessCheckTime); if (dwTimeSinceLastCheckInSeconds < BasePingInterval) { return; } // timestamp the check -- this time in ticks *gpdwLastCrashedProcessCheckTime = CurrentTime; #if DBG { OutputDebugString("Checked For Crashed Processes at:"); CHAR buffer[20]; wsprintfA(buffer, " %d\n", (*gpdwLastCrashedProcessCheckTime)/1000); OutputDebugString(buffer); OutputDebugString("Time Since Last Check:"); wsprintfA(buffer, " %d\n", dwTimeSinceLastCheckInSeconds); OutputDebugString(buffer); } #endif // now get together the table of currebntly running processes CPIDTable PidTable; HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0); if ((LONG)hSnap == -1) { ComDebOut((DEB_OXID,"CreateToolhelp32Snapshot failed with error %x", GetLastError())); return; } PROCESSENTRY32 processEntry; processEntry.dwSize = sizeof(processEntry); BOOL fContinue = FALSE; for ( fContinue = Process32First(hSnap,&processEntry); fContinue; fContinue = Process32Next(hSnap,&processEntry) ) { // These objects are ref counted and will be destroyed // when the table is destroyed at the end of the scope CPID *pPid = new CPID(processEntry.th32ProcessID); if (pPid==NULL) { CloseHandle(hSnap); return; // not much else we can do here } PidTable.Add(pPid); #if DBG OutputDebugString("Snapshot contains "); CHAR buffer[20]; wsprintfA(buffer, "%x ", processEntry.th32ProcessID); OutputDebugString(buffer); OutputDebugString(processEntry.szExeFile); OutputDebugString("\n"); #endif } CloseHandle(hSnap); // now iterate through our table of registered processes to // see if any of them are gone CProcessTableIterator procIter(*gpProcessTable); CProcess *pNextProcess; while (pNextProcess = procIter.Next()) { CPID Pid(pNextProcess->GetProcessID()); if (PidTable.Lookup(Pid) == NULL) // not running { ComDebOut((DEB_OXID,"Process PID = %x has crashed\n", pNextProcess->GetProcessID())); // run the process down before removing it -- removal may cause // its ref count to drop to zero leading to premature deletion pNextProcess->Rundown(); gpProcessTable->Remove(*pNextProcess); } else { ComDebOut((DEB_OXID,"Process PID = %d is running\n", pNextProcess->_processID)); } } } #define RUNDOWN_BATCH_SIZE 100 // This helper assumes that the gpMutex is held, // and releases it for the duration of the function call // The algorithm: Look through all candidate Oids to be // run down and try to run them down -- put the ones that // the Rundown interface OKs on the aRundownOids array. inline void RundownHelper( IRundown *pRemUnk, COid * aRundownOidCandidates[], DWORD cRundownOidCandidates, COid * aRundownOids[], DWORD &cRundownOids ) { unsigned char afOkToRundown[RUNDOWN_BATCH_SIZE]; OID aOIDs[RUNDOWN_BATCH_SIZE]; USHORT i; for (i =0; i < cRundownOidCandidates; i++) { aOIDs[i] = aRundownOidCandidates[i]->GetOID(); } { // Release shared memory so we can try real rundown without deadlock CTempReleaseSharedMemory temp; HRESULT hr = pRemUnk->RundownOid( cRundownOidCandidates, aOIDs, afOkToRundown ); ASSERT(hr == S_OK); } cRundownOids = 0; for (i = 0; i < cRundownOidCandidates; i++) { // the optimizer will take the hr == S_OK out of the loop, right? if (afOkToRundown[i]) { aRundownOids[cRundownOids++] = aRundownOidCandidates[i]; } else { // We don't care what happens to this any more // even though the shared mutex is not held right now aRundownOidCandidates[i]->Release(); } } } void COxid::RundownOidsIfNecessary( IRundown *pRemUnk ) { ::CheckForCrashedProcessesIfNecessary(); if (_MyOids.Size() == 0) return; COidTableIterator Oids(_MyOids); COid *pOid; // Hold a reference to self temorarily so we are not destroyed // during this call -- in spite of giving up the shared mutex CTempHoldRef tempRef(this); COid* aRundownOidCandidates[RUNDOWN_BATCH_SIZE]; DWORD cRundownOidCandidates; BOOL fCheckedAllOids = FALSE; // Find Oids that look ready to run down do { cRundownOidCandidates = 0; while (cRundownOidCandidates < RUNDOWN_BATCH_SIZE) { if ((pOid = Oids.Next()) == NULL) // We've been through all OIDs { fCheckedAllOids = TRUE; break; } if (pOid->OkToRundown()) { aRundownOidCandidates[cRundownOidCandidates++] = pOid; // We don't want to lose these, so hold a reference pOid->Reference(); } } if (cRundownOidCandidates == 0) { return; // No more work, just return } COid* bufRundownOids[RUNDOWN_BATCH_SIZE]; COid** aRundownOids; DWORD cRundownOids; if (IsLocal()) { aRundownOids = bufRundownOids; RundownHelper( pRemUnk, aRundownOidCandidates, cRundownOidCandidates, aRundownOids, cRundownOids ); } else { aRundownOids = aRundownOidCandidates; cRundownOids = cRundownOidCandidates; } // If while the lock was released, we stopped running, just return // The refs we are holding temporarily will be released by destructors if (!_fRunning) { return; } for (USHORT i = 0; i < cRundownOids; i++) { pOid = aRundownOids[i]; DisownOid(pOid); pOid->Release(); // release our private reference } } while (! fCheckedAllOids); }