2025-04-27 07:49:33 -04:00

859 lines
20 KiB
C++

/*++
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<or.hxx>
//
// 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 <tlhelp32.h>
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);
}