/*++ Copyright (c) 1995 Microsoft Corporation Module Name: Process.cxx Abstract: Process objects represent local clients and servers. These objects live as context handles. There are relatively few of these objects in the universe. Author: Mario Goertzel [MarioGo] Revision History: MarioGo 02-20-95 Bits 'n pieces Ronans 20-02-97 UseProtseqIfNeeded modified for custom endpoints Ronans 20-02-97 Added custom endpoints stuff to process object TarunA 09-Nov-98 Added process handle --*/ #include CRITICAL_SECTION gcsFastProcessLock; extern HRESULT FreeSPIFromCProcess(void** ppSCMProcessInfo); const DWORD BINDINGUPDATESTUFF_SIG = 0xFEDCBA01; typedef struct _BINDINGS_UPDATE_CALLBACK_STUFF { DWORD dwSig; // see BINDINGUPDATESTUFF_SIG above // housekeeping stuff CProcess* pProcess; // has reference while call is in-flight RPC_BINDING_HANDLE hBinding; RPC_ASYNC_STATE async; // out and in-out params DWORD64 dwBindingsID; DUALSTRINGARRAY* pdsaNewBindings; DUALSTRINGARRAY* pdsaNewSecurity; } BINDINGS_UPDATE_CALLBACK_STUFF; const DWORD ASYNCRUNDOWNOID_SIG = 0xFEDCBA02; typedef struct _ASYNCRUNDOWNOID_STUFF { DWORD dwSig; // see ASYNCRUNDOWNOID_SIG above // housekeeping stuff CProcess* pProcess; // has reference while call is in-flight CServerOxid* pOxid; // has reference while call is in-flight RPC_BINDING_HANDLE hBinding; RPC_ASYNC_STATE async; // We keep these for when we process the return ULONG cOids; CServerOid* aOids[MAX_OID_RUNDOWNS_PER_CALL]; // The ORPC params are reference pointers, so they must // stay alive for the life of the call ORPCTHIS orpcthis; LOCALTHIS localthis; ORPCTHAT orpcthat; INT callIDHint; // need to free this on return // in-out or out params. BYTE aRundownStatus[MAX_OID_RUNDOWNS_PER_CALL]; } ASYNCRUNDOWNOID_STUFF; void CProcess::Rundown() /*++ Routine Description: The client process has rundown. This means there are no more client refernces which means we are free to clean things up as long as server OXIDs still holding references won't get upset. They all use the server lock when accessing the process. Arguments: None Return Value: None --*/ { ORSTATUS status; gpServerLock->LockExclusive(); ASSERT(_cClientReferences == 0); KdPrintEx((DPFLTR_DCOMSS_ID, DPFLTR_INFO_LEVEL, "OR: Rundown of %p: %d oxids, %d oids and %d roxids left\n", this, _blistOxids.Size(), _blistOids.Size())); // Release any OXIDs owned by this process. This may destroy the OXID. // This will release this CProcess, but won't release the last reference as // the client process still owns one. if (_blistOxids.Size()) { CServerOxid *poxid; CBListIterator oxids(&_blistOxids); while (poxid = (CServerOxid *)oxids.Next()) { gpServerOxidTable->Remove(poxid); poxid->ProcessRelease(); } } // Release any OIDs is use by this processes. // Do this now, rather then waiting for the last server oid // owned by this process to get invalidated and rundown. gpClientLock->LockExclusive(); // *** Both client and server lock held here. *** if (_blistOids.Size()) { CClientOid *poid; CBListIterator oids(&_blistOids); while (poid = (CClientOid *)oids.Next()) { ASSERT(NULL != poid->GetClientOxid()); poid->GetClientOxid()->Release(); poid->ClientRelease(); } } // Cleanup other process state. Note: it is important that the // release of the process and token handle never happen any later // than rundown time. If the process handle is released any later, you // will see bugs like "can't recompile my com server" after they run // it once. If the token handle is released any later, you will get // security bugs from the NT security folks since we will hold onto // the logged-on user's token until many minutes after logoff. // // Ask me how I know this... // if (_hProcHandle) { CloseHandle(_hProcHandle); _hProcHandle = NULL; } if (_pToken) { _pToken->Release(); _pToken = 0; } // Free the cached SCMProcessInfo if we have one FreeSPIFromCProcess(&_pSCMProcessInfo); // Flip the rundown and dirty bit _dwFlags |= (PROCESS_RUNDOWN & PROCESS_SPI_DIRTY); gpClientLock->UnlockExclusive(); // Done, release the clients' reference, this may actually delete this // process. (If an OXID still exists and has OIDs it will not be deleted // until the OIDs all rundown). this->Release(); // The this pointer maybe invalid now. gpServerLock->UnlockExclusive(); } CProcess::CProcess( IN CToken*& pToken, IN WCHAR *pwszWinstaDesktop, IN DWORD procID, IN DWORD dwFlags, OUT ORSTATUS &status ) : _blistOxids(4), _blistOids(16), _listClasses() /*++ Routine Description: Initalized a process object, members and add it to the process list. Arguments: pToken - The clients token. We assume we have a reference. pwszWinstaDesktop - The client's windowstation/desktop string. procID - The client's process ID. status - Sometimes the C'tor can fail with OR_NOMEM. Return Value: None --*/ { _cClientReferences = 1; _hProcess = NULL; _fCacheFree = FALSE; _pdsaLocalBindings = NULL; _pdsaRemoteBindings = NULL; _pToken = NULL; _pwszWinstaDesktop = NULL; _pScmProcessReg = NULL; _pvRunAsHandle = NULL; _procID = 0; _fLockValid = FALSE; _hProcHandle = NULL; _dwFlags = PROCESS_SPI_DIRTY; // always start out dirty _pSCMProcessInfo = NULL; _ulClasses = 0; _dwCurrentBindingsID = 0; _dwAsyncUpdatesOutstanding = 0; // Set 64 bit flag if so indicated if (dwFlags & CONNECT_FLAGS_64BIT) _dwFlags |= PROCESS_64BIT; // Store time of object creation. Note that this is in UTC time. GetSystemTimeAsFileTime(&_ftCreated); // Generate a unique guid to represent this process UuidCreate(&_guidProcessIdentifier); // ronans - entries for custom protseqs from server // not used for clients _fReadCustomProtseqs = FALSE; _pdsaCustomProtseqs = NULL; status = OR_OK; if ( pwszWinstaDesktop == NULL ) { status = OR_BADPARAM; } if (status == OR_OK) { status = RtlInitializeCriticalSection(&_csCallbackLock); _fLockValid = (status == STATUS_SUCCESS); } if (status == STATUS_SUCCESS) { gpProcessListLock->LockExclusive(); status = gpProcessList->Insert(this); gpProcessListLock->UnlockExclusive(); } if (status == OR_OK) { _pwszWinstaDesktop = new WCHAR[OrStringLen(pwszWinstaDesktop)+1]; if (! _pwszWinstaDesktop) status = OR_NOMEM; else OrStringCopy(_pwszWinstaDesktop, pwszWinstaDesktop); } if (status == OR_OK) { _pToken = pToken; pToken = NULL; // We've taken the token } _procID = procID; #if DBG _cRundowns = 0; #endif } CProcess::~CProcess(void) // You probably should be looking in the ::Rundown method. // This process object stays alive until the last server oxid dies. { ASSERT(gpServerLock->HeldExclusive()); ASSERT(_hProcHandle == 0); ASSERT(_pSCMProcessInfo == 0); delete _pdsaLocalBindings; delete _pdsaRemoteBindings; MIDL_user_free( _pdsaCustomProtseqs ); delete _pwszWinstaDesktop; if (_fLockValid) DeleteCriticalSection(&_csCallbackLock); if (_hProcess) { RPC_STATUS status = RpcBindingFree(&_hProcess); ASSERT(status == RPC_S_OK); ASSERT(_hProcess == 0); } #ifndef _CHICAGO_ extern void RunAsRelease(void*); RunAsRelease(_pvRunAsHandle); #endif // _CHICAGO_ return; } // // SetSCMProcessInfo // // Swaps our old cached SCMProcessInfo* for a new one. // HRESULT CProcess::SetSCMProcessInfo(void* pSPI) { ASSERT(gpServerLock->HeldExclusive()); ASSERT(!(_dwFlags & PROCESS_RUNDOWN)); if (_dwFlags & PROCESS_RUNDOWN) return E_UNEXPECTED; FreeSPIFromCProcess(&_pSCMProcessInfo); // set the new one _pSCMProcessInfo = pSPI; // this means we're no longer dirty _dwFlags &= ~PROCESS_SPI_DIRTY; return S_OK; } void CProcess::SetProcessReadyState(DWORD dwState) { ASSERT(_pScmProcessReg); gpServerLock->LockExclusive(); _pScmProcessReg->ReadinessStatus = dwState; // we're now dirty _dwFlags |= PROCESS_SPI_DIRTY; gpServerLock->UnlockExclusive(); } void CProcess::Retire() { // A process can (or should be) only retired once ASSERT(!IsRetired()); // Mark ourselves as retired _dwFlags |= (PROCESS_RETIRED | PROCESS_SPI_DIRTY); } void CProcess::SetRunAsHandle(void *pvRunAsHandle) { ASSERT(!_pvRunAsHandle); _pvRunAsHandle = pvRunAsHandle; } BOOL CProcess::SetProcessHandle(HANDLE hProcHandle, DWORD dwLaunchedPID) { /*++ Routine Description: Store the handle of the process only if the process launched by us and the process registering back are the same. Otherwise we might kill a process not launched by us on receiving certain error conditions (notably RPC_E_SERVERFAULT) Arguments: hProcHandle - Handle of the process launched dwLaunchedPID - PID of the process launched Return Value: TRUE - If handle is set FALSE - otherwise --*/ if (dwLaunchedPID == _procID) { _hProcHandle = hProcHandle; return TRUE; } return FALSE; } RPC_STATUS CProcess::ProcessBindings( IN DUALSTRINGARRAY *pdsaStringBindings, IN DUALSTRINGARRAY *pdsaSecurityBindings ) /*++ Routine Description: Updates the string and optionally the security bindings associated with this process. Arguments: psaStringBindings - The expanded string bindings of the process psaSecurityBindings - compressed security bindings of the process. If NULL, the current security bindings are reused. Environment: Server lock held during call or called from an OXID with an extra reference owned by the process and keeping this process alive. Return Value: OR_NOMEM - unable to allocate storage for the new string arrays. OR_OK - normally. --*/ { CMutexLock lock(&gcsFastProcessLock); USHORT wSecSize; PWSTR pwstrSecPointer; // NULL security bindings means we should use the existing bindings. if (0 == pdsaSecurityBindings) { ASSERT(_pdsaLocalBindings); wSecSize = _pdsaLocalBindings->wNumEntries - _pdsaLocalBindings->wSecurityOffset; pwstrSecPointer = _pdsaLocalBindings->aStringArray + _pdsaLocalBindings->wSecurityOffset; } else { wSecSize = pdsaSecurityBindings->wNumEntries - pdsaSecurityBindings->wSecurityOffset; pwstrSecPointer = &pdsaSecurityBindings->aStringArray[pdsaSecurityBindings->wSecurityOffset]; } DUALSTRINGARRAY *pdsaT = CompressStringArrayAndAddIPAddrs(pdsaStringBindings); if (!pdsaT) { return(OR_NOMEM); } // ignore security on string binding parameter pdsaT->wNumEntries = pdsaT->wSecurityOffset; DUALSTRINGARRAY *pdsaResult = new((pdsaT->wNumEntries + wSecSize) * sizeof(WCHAR)) DUALSTRINGARRAY; if (0 == pdsaResult) { delete pdsaT; return(OR_NOMEM); } pdsaResult->wNumEntries = pdsaT->wNumEntries + wSecSize; pdsaResult->wSecurityOffset = pdsaT->wSecurityOffset; OrMemoryCopy(pdsaResult->aStringArray, pdsaT->aStringArray, pdsaT->wSecurityOffset*sizeof(WCHAR)); OrMemoryCopy(pdsaResult->aStringArray + pdsaResult->wSecurityOffset, pwstrSecPointer, wSecSize*sizeof(WCHAR)); ASSERT(dsaValid(pdsaResult)); delete pdsaT; delete _pdsaLocalBindings; _pdsaLocalBindings = pdsaResult; delete _pdsaRemoteBindings; _pdsaRemoteBindings = 0; return(RPC_S_OK); } DUALSTRINGARRAY * CProcess::GetLocalBindings(void) // Server lock held or called within an // OXID with an extra reference. { CMutexLock lock(&gcsFastProcessLock); if (0 == _pdsaLocalBindings) { return(0); } DUALSTRINGARRAY *T = (DUALSTRINGARRAY *)MIDL_user_allocate(sizeof(DUALSTRINGARRAY) + sizeof(USHORT) * _pdsaLocalBindings->wNumEntries); if (0 != T) { dsaCopy(T, _pdsaLocalBindings); } return(T); } DUALSTRINGARRAY * CProcess::GetRemoteBindings(void) // Server lock held. { CMutexLock lock(&gcsFastProcessLock); ORSTATUS Status; if (0 == _pdsaRemoteBindings) { if (0 == _pdsaLocalBindings) { return(0); } Status = ConvertToRemote(_pdsaLocalBindings, &_pdsaRemoteBindings); if (Status != OR_OK) { ASSERT(Status == OR_NOMEM); return(0); } ASSERT(dsaValid(_pdsaRemoteBindings)); } DUALSTRINGARRAY *T = (DUALSTRINGARRAY *)MIDL_user_allocate(sizeof(DUALSTRINGARRAY) + sizeof(USHORT) * _pdsaRemoteBindings->wNumEntries); if (0 != T) { dsaCopy(T, _pdsaRemoteBindings); } ASSERT(dsaValid(T)); return(T); } ORSTATUS CProcess::AddOxid(CServerOxid *pOxid) { ASSERT(gpServerLock->HeldExclusive()); pOxid->Reference(); ASSERT(_blistOxids.Member(pOxid) == FALSE); ORSTATUS status = _blistOxids.Insert(pOxid); if (status != OR_OK) { pOxid->ProcessRelease(); return(status); } gpServerOxidTable->Add(pOxid); return(OR_OK); } BOOL CProcess::RemoveOxid(CServerOxid *poxid) { ASSERT(gpServerLock->HeldExclusive()); CServerOxid *pit = (CServerOxid *)_blistOxids.Remove(poxid); if (pit) { ASSERT(pit == poxid); gpServerOxidTable->Remove(poxid); poxid->ProcessRelease(); return(TRUE); } return(FALSE); } BOOL CProcess::IsOwner(CServerOxid *poxid) { ASSERT(gpServerLock->HeldExclusive()); return(_blistOxids.Member(poxid)); } ORSTATUS CProcess::AddOid(CClientOid *poid) /*++ Routine Description: Adds a new oid to the list of OIDs owned by this process and increments the reference count of the associated OXID Arguments: poid - the oid to add. It's reference is transferred to this function. If this function fails, it must dereference the oid. The caller passed a client reference to this process. The process must eventually call ClientRelease() on the parameter. Return Value: OR_OK - normally OR_NOMEM - out of memory. --*/ { ORSTATUS status; ASSERT(gpClientLock->HeldExclusive()); status = _blistOids.Insert(poid); if (status != OR_OK) { ASSERT(status == OR_NOMEM); poid->ClientRelease(); } else { ASSERT(NULL != poid->GetClientOxid()); poid->GetClientOxid()->Reference(); } return(status); } CClientOid * CProcess::RemoveOid(CClientOid *poid) /*++ Routine Description: Removes an OID from this list of OID in use by this process. Arguments: poid - The OID to remove. Return Value: non-zero - the pointer actually remove. (ASSERT(retval == poid)) It will be released by the process before return, so you should not use the pointer unless you know you have another reference. 0 - not in the list --*/ { ASSERT(gpClientLock->HeldExclusive()); CClientOid *pit = (CClientOid *)_blistOids.Remove(poid); if (pit) { ASSERT(pit == poid); pit->ClientRelease(); ASSERT(NULL != poid->GetClientOxid()); poid->GetClientOxid()->Release(); return(pit); } return(0); } void CProcess::AddClassReg(GUID & Guid, DWORD Reg) { CClassReg * pReg; pReg = new CClassReg( Guid, Reg ); if (pReg) { gpServerLock->LockExclusive(); _listClasses.Insert( pReg ); // flip the dirty bit _dwFlags |= PROCESS_SPI_DIRTY; _ulClasses++; gpServerLock->UnlockExclusive(); } } void CProcess::RemoveClassReg(DWORD Reg) { CClassReg * pReg; gpServerLock->LockExclusive(); pReg = (CClassReg *)_listClasses.First(); while ( (pReg != NULL) && (pReg->_Reg != Reg) ) pReg = (CClassReg *)pReg->Next(); if (pReg) { (void)_listClasses.Remove( pReg ); delete pReg; // flip the dirty bit _dwFlags |= PROCESS_SPI_DIRTY; _ulClasses--; } gpServerLock->UnlockExclusive(); } void CProcess::Cleanup() { SCMProcessCleanup(this); } void CProcess::RevokeClassRegs() { if (_pScmProcessReg) { // This is a unified surrogate (COM+) server SCMRemoveRegistration(_pScmProcessReg); _pScmProcessReg = NULL; } // This is for legacy local or custom surrogate servers -- however, // nothing prevents someone from calling CoRegisterClassObject in // user code even in a COM+ (surrogate) server CClassReg * pReg; // This is only called during rundown so we don't have to take a lock. while ( (pReg = (CClassReg *)_listClasses.First()) != 0 ) { (void)_listClasses.Remove((CListElement *)pReg); SCMRemoveRegistration( this, pReg->_Guid, pReg->_Reg ); delete pReg; } } void CProcess::SetProcessReg(ScmProcessReg *pProcessReg) /*++ Routine Description: Called by SCM to set COM+ process registration. This is also used as a cache during the startup protocol to query and set the readiness state of the server process. There is exactly one such registration per COM+ server process. CODEWORK: these should be inlined Arguments: registration struct Return Value: none. --*/ { gpServerLock->LockExclusive(); _pScmProcessReg = pProcessReg; gpServerLock->UnlockExclusive(); } ScmProcessReg* CProcess::GetProcessReg() /*++ Routine Description: Called by SCM to lookup COM+ process registration. Arguments: None Return Value: registration struct, if any. --*/ { gpServerLock->LockShared(); ScmProcessReg *pResult = _pScmProcessReg; gpServerLock->UnlockShared(); return pResult; } ScmProcessReg* CProcess::RemoveProcessReg() /*++ Routine Description: Called by SCM when COM+ process revokes its activator registration. Arguments: None Return Value: none. --*/ { gpServerLock->LockExclusive(); ScmProcessReg *pResult = _pScmProcessReg; // Even if this process was a new-style surrogate process, _pScmProcessReg // may already be NULL here. See bug 26676. So we don't assert anymore // that _pScmProcessReg is non-NULL. // ASSERT(_pScmProcessReg); _pScmProcessReg = NULL; gpServerLock->UnlockExclusive(); return pResult; } void CProcess::RevokeProcessReg() /*++ Routine Description: Called during rundown to let SCM know that the COM+ process has died. CODEWORK: This needs to be defined. Arguments: None Return Value: none. --*/ { } RPC_BINDING_HANDLE CProcess::GetBindingHandle( void ) /*++ Routine Description: If necessary, this function allocates a binding handle back to process. It used either mswmsg or ncalrpc depending on the apartmentness of the process. Arguments: None Return Value: Binding Handle, NULL if no valid handle. --*/ { RPC_STATUS status; CMutexLock lock(&gcsFastProcessLock); // Find ncalrpc binding. PWSTR pwstr = _pdsaLocalBindings->aStringArray; while (*pwstr) { if (*pwstr == ID_LPC) { break; } pwstr = OrStringSearch(pwstr, 0) + 1; } if (*pwstr == 0) { KdPrintEx((DPFLTR_DCOMSS_ID, DPFLTR_WARNING_LEVEL, "OR: Unable to find ncalrpc binding to server: %p %p\n", _pdsaLocalBindings, this)); ASSERT(0); return NULL; } return GetBinding(pwstr); } void CProcess::EnsureRealBinding( void ) /*++ Routine Description: If necessary, this function allocates a binding handle back to process. Note: Called with the server lock held -OR- from an OXID with and extra reference which keeps this process alive. Arguments: None Return Value: None --*/ { CMutexLock lock(&gcsFastProcessLock); if (0 == _hProcess) { _hProcess = GetBindingHandle(); _fCacheFree = TRUE; } } RPC_BINDING_HANDLE CProcess::AllocateBinding( void ) /*++ Routine Description: Allocates a unique binding handle for a call back to the process. This binding handle will not be used by another thread until it is freed. Arguments: None Return Value: 0 - failure non-zero - a binding handle to use. --*/ { EnsureRealBinding(); if (_hProcess == 0) { return(0); } CMutexLock lock(&gcsFastProcessLock); ASSERT(_hProcess); if (_fCacheFree) { _fCacheFree = FALSE; return(_hProcess); } RPC_BINDING_HANDLE h; RPC_STATUS status; status = RpcBindingCopy(_hProcess, &h); if (status != RPC_S_OK) { return(0); } return(h); } void CProcess::FreeBinding( IN RPC_BINDING_HANDLE hBinding ) /*++ Routine Description: Frees a binding back to the process. Arguments: hBinding - A binding back to the process previously allocated with AllocateBinding(). Return Value: None --*/ { if (hBinding == _hProcess) { _fCacheFree = TRUE; } else { RPC_STATUS status = RpcBindingFree(&hBinding); ASSERT(status == RPC_S_OK); } } RPC_STATUS CProcess::RundownOids( IN CServerOxid* pOwningOxid, IN ULONG cOids, IN CServerOid* aOids[] ) /*++ Routine Description: Issues an async call to the process which will rundown the OIDs. This is called from an OXID which will be kept alive during the whole call. Multiple calls maybe made to this function by one or more OXIDs at the same time. The callback itself is an ORPC call, ie is must have THIS and THAT pointers. Arguments: pOwningOxid - oxid that owns the specified oids. This oxid should be registered from this process. cOids - The number of entries in aOids and afRundownOk aOids - An array of CServerOid's to rundown. The OIDs must all be owned by pOwningOxid. The caller will have already called SetRundown(TRUE) on each one. Return Value: RPC_S_OK - the async call was issued successfully other -- error occurred. call was not issued. --*/ { ULONG i; error_status_t status = OR_OK; RPC_BINDING_HANDLE hBinding; OID aScalarOids[MAX_OID_RUNDOWNS_PER_CALL]; ASSERT(cOids > 0 && aOids); ASSERT(cOids <= MAX_OID_RUNDOWNS_PER_CALL); ASSERT(IsOwner(pOwningOxid)); // Callers must be aware that the lock will be released upon return ASSERT(gpServerLock->HeldExclusive()); gpServerLock->UnlockExclusive(); // This process will be held alive by the OXID calling // us since it has an extra reference. // // Allocate async structure and zero it out // ASYNCRUNDOWNOID_STUFF* pArgs = new ASYNCRUNDOWNOID_STUFF; if (!pArgs) return OR_NOMEM; ZeroMemory(pArgs, sizeof(ASYNCRUNDOWNOID_STUFF)); // // Initialize async structure // pArgs->dwSig = ASYNCRUNDOWNOID_SIG; pArgs->pProcess = this; // will take ref below after issuing call pArgs->pOxid = pOwningOxid; // will take ref below after issuing call pArgs->cOids = cOids; CopyMemory(pArgs->aOids, aOids, cOids * sizeof(CServerOid*)); // // Fill in the numeric oid values // ZeroMemory(aScalarOids, sizeof(OID) * MAX_OID_RUNDOWNS_PER_CALL); for (i = 0; i < cOids; i++) { ASSERT(pArgs->aOids[i]); ASSERT(pArgs->aOids[i]->IsRunningDown()); ASSERT(pArgs->aOids[i]->GetOxid() == pOwningOxid); aScalarOids[i] = pArgs->aOids[i]->Id(); } // // Allocate binding handle // status = OR_NOMEM; // assume no mem hBinding = AllocateBinding(); if (hBinding) { IPID ipidUnk = pOwningOxid->GetIPID(); status = RpcBindingSetObject(hBinding, &ipidUnk); if (status == RPC_S_OK) { status = RpcAsyncInitializeHandle(&(pArgs->async), sizeof(pArgs->async)); } } // // Check for errors // if (status != RPC_S_OK) { if (hBinding) FreeBinding(hBinding); delete pArgs; return status; } // // Save the binding handle // pArgs->hBinding = hBinding; // // Init parts of the RPC_ASYNC_STATE struct that we care about // pArgs->async.UserInfo = pArgs; pArgs->async.Event = RpcCallComplete; pArgs->async.NotificationType = RpcNotificationTypeCallback; pArgs->async.u.NotificationRoutine = CProcess::AsyncRundownReturnNotification; // // Initialize other params // pArgs->orpcthis.version.MajorVersion = COM_MAJOR_VERSION; pArgs->orpcthis.version.MinorVersion = COM_MINOR_VERSION; pArgs->orpcthis.flags = ORPCF_LOCAL; pArgs->orpcthis.reserved1 = 0; pArgs->orpcthis.extensions = NULL; pArgs->callIDHint = AllocateCallId(pArgs->orpcthis.cid); pArgs->localthis.dwClientThread = 0; pArgs->localthis.dwFlags = LOCALF_NONE; pArgs->orpcthat.flags = 0; pArgs->orpcthat.extensions = 0; // // Take an extra reference on the owning oxid and ourself. // These references will be released either on the call // return notification, or on the failure path below. // pOwningOxid->Reference(); this->Reference(); // non-client ref, will not stop rundown // // Finally, issue the call. Note that this is an async call // from our perspective, but is a synchronous ORPC call from // the server's perspective. // status = RawRundownOid( &(pArgs->async), pArgs->hBinding, &(pArgs->orpcthis), &(pArgs->localthis), &(pArgs->orpcthat), pArgs->cOids, aScalarOids, pArgs->aRundownStatus ); if (status != RPC_S_OK) { // Call failed, so cleanup before returning. Caller will // handle the failure semantics for the oids. FreeBinding(pArgs->hBinding); FreeCallId(pArgs->callIDHint); delete pArgs; // // Must hold gpServerLock in order to call Release. // ASSERT(!gpServerLock->HeldExclusive()); gpServerLock->LockExclusive(); pOwningOxid->Release(); this->Release(); gpServerLock->UnlockExclusive(); ASSERT(!gpServerLock->HeldExclusive()); } return status; } void CProcess::RundownOidNotify(RPC_BINDING_HANDLE hBinding, CServerOxid* pOwningOxid, ULONG cOids, CServerOid* aOids[], BYTE aRundownStatus[], HRESULT hrReturn) /*++ Routine Description: This is the callback notification function that is invoked when async oid rundown calls are completed. Arguments: hBinding -- binding handle used to make the call pOwningOxid -- owning oxid of the oids we tried to rundown. cOids -- count of oids aOids -- array of oids aRundownStatus -- array of individual status rundowns for each oid hrReturn -- return value from the function Return Value: void --*/ { ULONG i; error_status_t status = OR_OK; ASSERT(hBinding); ASSERT(pOwningOxid); ASSERT((cOids > 0) && aOids && aRundownStatus); // // Free the binding // FreeBinding(hBinding); // // If destination oxid\apartment was not found, mark all // oids for rundown. // if (hrReturn == RPC_E_DISCONNECTED) { KdPrintEx((DPFLTR_DCOMSS_ID, DPFLTR_WARNING_LEVEL, "OR: Rundown returned disconnected\n")); for (i = 0; i < cOids; i++) { aRundownStatus[i] = ORS_OK_TO_RUNDOWN; } hrReturn = RPC_S_OK; } // // In case of any other error, don't rundown the oids // if (hrReturn != RPC_S_OK) { for (i = 0; i < cOids; i++) { aRundownStatus[i] = ORS_DONTRUNDOWN; } } // // Notify the server oxid of the results // gpServerLock->LockExclusive(); pOwningOxid->ProcessRundownResults( cOids, aOids, aRundownStatus ); gpServerLock->UnlockExclusive(); return; } void RPC_ENTRY CProcess::AsyncRundownReturnNotification( IN RPC_ASYNC_STATE* pAsync, IN void* pContext, IN RPC_ASYNC_EVENT Event ) /*++ Routine Description: This is the callback notification function that is invoked when async rundown calls are completed. It unpacks the necessary stuff from the async args struct, forwards them on to RundownOidNotify, and does other necessary cleanup. Arguments: pAsync -- pointer to the async rpc state struct pContext -- rpc thingy, we ignore it Event -- rpc thingy, we ignore it Return Value: void --*/ { RPC_STATUS status; HRESULT hrRetVal; ASYNCRUNDOWNOID_STUFF* pArgs; pArgs = (ASYNCRUNDOWNOID_STUFF*) (((char*)pAsync) - offsetof(ASYNCRUNDOWNOID_STUFF, async)); ASSERT(pArgs->async.UserInfo = pArgs); ASSERT(pArgs->dwSig == ASYNCRUNDOWNOID_SIG); ASSERT(pArgs->hBinding); ASSERT(pArgs->pProcess); ASSERT(pArgs->pOxid); ASSERT(pArgs->cOids > 0); // // Free the call id // FreeCallId(pArgs->callIDHint); // Complete the call. Since we've asked for a direct callback upon // completion, we should never get back RPC_S_ASYNC_CALL_PENDING, so // we assert on this. hrRetVal is not necessarily S_OK. status = RpcAsyncCompleteCall(&(pArgs->async), &hrRetVal); ASSERT(status != RPC_S_ASYNC_CALL_PENDING); // // Notify the process object that the call has returned // pArgs->pProcess->RundownOidNotify( pArgs->hBinding, // process frees the binding pArgs->pOxid, pArgs->cOids, pArgs->aOids, pArgs->aRundownStatus, (status == RPC_S_OK) ? hrRetVal : status ); // // Cleanup other stuff from the call // if (status == RPC_S_OK && pArgs->orpcthat.extensions) { for (ULONG i = 0; i < pArgs->orpcthat.extensions->size; i++) { MIDL_user_free(pArgs->orpcthat.extensions->extent[i]); } MIDL_user_free(pArgs->orpcthat.extensions->extent); MIDL_user_free(pArgs->orpcthat.extensions); } // // Release the references on ourselves and the server // oxid. Must hold gpServerLock while calling Release. // gpServerLock->LockExclusive(); pArgs->pProcess->Release(); pArgs->pOxid->Release(); gpServerLock->UnlockExclusive(); delete pArgs; return; } ORSTATUS CProcess::UseProtseqIfNeeded( IN USHORT cClientProtseqs, IN USHORT aClientProtseqs[] ) { ORSTATUS status; RPC_BINDING_HANDLE hBinding; UUID NullUuid = {0}; USHORT wProtseqTowerId; KdPrintEx((DPFLTR_DCOMSS_ID, DPFLTR_WARNING_LEVEL, "OR: UseProtseqIfNeeded ==> %d\n", cClientProtseqs)); // This process will be held alive by the OXID calling // us since it has an extra reference. CMutexLock callback(&_csCallbackLock); CMutexLock process(&gcsFastProcessLock); // Another thread may have used the protseq in the mean time. ASSERT(_pdsaLocalBindings); KdPrintEx((DPFLTR_DCOMSS_ID, DPFLTR_WARNING_LEVEL, "OR: FindMatchingProtSeq from local bindings\n")); // ronans - initially _pdsaLocalBindings will hold bindings which have been set // by when the server called ServerAllocateOxidAndOids .. usually only local // protseqs LRPC or WMSG at that point. wProtseqTowerId = FindMatchingProtseq(cClientProtseqs, aClientProtseqs, _pdsaLocalBindings->aStringArray ); if (0 != wProtseqTowerId) { KdPrintEx((DPFLTR_DCOMSS_ID, DPFLTR_WARNING_LEVEL, "OR: Found protseq in local bindings\n")); return(OR_OK); } // No protseq shared between the client and the OXIDs' server. // Find a matching protseq. // check if its a solitary local protocol sequence LRPC or WMSG if (cClientProtseqs == 1 && IsLocal(aClientProtseqs[0])) { // if so - get it wProtseqTowerId = aClientProtseqs[0]; ASSERT(wProtseqTowerId); } else // we have multiple protseqs - presumed to be nonlocal { // ensure we have custom protseq information if (!_fReadCustomProtseqs) { // use local temporary to avoid holding process lock DUALSTRINGARRAY *pdsaCustomProtseqs = NULL; // we'll only try this once - so set the flag now to avoid // race conditions _fReadCustomProtseqs = TRUE; process.Unlock(); hBinding = AllocateBinding(); if (0 == hBinding) return(OR_NOMEM); status = RpcBindingSetObject(hBinding, &NullUuid); // get the information from the Object server if (status == RPC_S_OK) { status = ::GetCustomProtseqInfo(hBinding,cMyProtseqs, aMyProtseqs, &pdsaCustomProtseqs); KdPrintEx((DPFLTR_DCOMSS_ID, DPFLTR_WARNING_LEVEL, "GetCustomProtseqInfo - status : %ld\n", status)); } // relock the process object process.Lock(); if (status == RPC_S_OK) { if (pdsaCustomProtseqs) { ASSERT(dsaValid(pdsaCustomProtseqs)); } _pdsaCustomProtseqs = pdsaCustomProtseqs; } FreeBinding(hBinding); } USHORT i,j; // if there is custom protseq information - scan it for a match if (_pdsaCustomProtseqs) { KdPrintEx((DPFLTR_DCOMSS_ID, DPFLTR_WARNING_LEVEL, "OR: Using custom protseq information\n")); wProtseqTowerId = FindMatchingProtseq(cClientProtseqs, aClientProtseqs, _pdsaCustomProtseqs->aStringArray); if (wProtseqTowerId) { ASSERT(FALSE == IsLocal(wProtseqTowerId)); } } else // we don't have custom protseqs so use // the standard ones { KdPrintEx((DPFLTR_DCOMSS_ID, DPFLTR_WARNING_LEVEL, "OR: Using standard protseq information\n")); for (i = 0; i < cClientProtseqs && wProtseqTowerId == 0; i++) { for (j = 0; j < cMyProtseqs; j++) { if (aMyProtseqs[j] == aClientProtseqs[i]) { ASSERT(FALSE == IsLocal(aMyProtseqs[j])); wProtseqTowerId = aMyProtseqs[j]; break; } } } } } if (0 == wProtseqTowerId) { // No shared protseq, must be a bug since the client managed to call us. ASSERT(0 && "No shared protseq, must be a bug since the client managed to call us"); #if DBG if (cClientProtseqs == 0) KdPrintEx((DPFLTR_DCOMSS_ID, DPFLTR_WARNING_LEVEL, "OR: Client OR not configured to use remote protseqs\n")); else KdPrintEx((DPFLTR_DCOMSS_ID, DPFLTR_WARNING_LEVEL, "OR: Client called on an unsupported protocol:" "%d %p %p \n", cClientProtseqs, aClientProtseqs, aMyProtseqs)); #endif return(OR_NOSERVER); } process.Unlock(); DUALSTRINGARRAY *pdsaBinding = 0; DUALSTRINGARRAY *pdsaSecurity = 0; hBinding = AllocateBinding(); if (0 == hBinding) { return(OR_NOMEM); } status = RpcBindingSetObject(hBinding, &NullUuid); if (status == RPC_S_OK) { status = ::UseProtseq(hBinding, wProtseqTowerId, &pdsaBinding, &pdsaSecurity); } KdPrintEx((DPFLTR_DCOMSS_ID, DPFLTR_WARNING_LEVEL, "OR: Lazy use protseq: %S (from towerid) in process %p - %d\n", GetProtseq(wProtseqTowerId), this, status)); // Update this process' state to include the new bindings. if (!dsaValid(pdsaBinding)) { if (pdsaBinding) { KdPrintEx((DPFLTR_DCOMSS_ID, DPFLTR_WARNING_LEVEL, "OR: Use protseq returned an invalid dsa: %p\n", pdsaBinding)); } status = OR_NOMEM; } else { ASSERT(_pdsaLocalBindings); ASSERT(status == RPC_S_OK); status = ProcessBindings(pdsaBinding, pdsaSecurity); } if (pdsaBinding != NULL) MIDL_user_free(pdsaBinding); if (pdsaSecurity != NULL) MIDL_user_free(pdsaSecurity); FreeBinding(hBinding); return(status); } ORSTATUS CProcess::UpdateResolverBindings(DWORD64 dwBindingsID, DUALSTRINGARRAY* pdsaResolverBindings) /*++ Routine Description: This function does the work of calling back into the server process to tell it to update its local OR bindings. It also updates our cached local\remote bindings for this process object. Arguments: dwBindingsID -- unique id of pdsaResolverBindings. We use this to resolve what to do when async calls arrive\return in an out-of-order fashion. pdsaResolverBindings -- ptr to the new resolver bindings Return Value: OR_OK -- success OR_NOMEM -- out of memory other -- unexpected error --*/ { ORSTATUS status; RPC_BINDING_HANDLE hBinding; UUID NullUuid = {0}; DUALSTRINGARRAY* pdsaBinding = NULL; DUALSTRINGARRAY* pdsaSecurity = NULL; BINDINGS_UPDATE_CALLBACK_STUFF* pArgs = NULL; // undone figure out why we need this callback lock thingy CMutexLock callback(&_csCallbackLock); if (dwBindingsID <= _dwCurrentBindingsID) { // The supplied bindings are the same or older than // what we already have, so ignore them. return OR_OK; } // if the server has not yet called _ServerAllocateOxidAndOids // then we will not yet have cached binding info for them, and // hence will be unable to construct a binding handle. In // this case we simply do not update that process. If this // happens we will give them the new bindings later, if and // when they do call _SAOAO. if (!_pdsaLocalBindings) { _dwFlags |= PROCESS_NEEDSBINDINGS; return OR_OK; } pArgs = new BINDINGS_UPDATE_CALLBACK_STUFF; if (!pArgs) return OR_NOMEM; ZeroMemory(pArgs, sizeof(BINDINGS_UPDATE_CALLBACK_STUFF)); // Allocate binding. We must hold onto the binding handle // until the async call has completed. status = OR_NOMEM; hBinding = AllocateBinding(); if (hBinding) { status = RpcBindingSetObject(hBinding, &NullUuid); if (status == RPC_S_OK) { status = RpcAsyncInitializeHandle(&(pArgs->async), sizeof(pArgs->async)); } } // Check for errors if (status != RPC_S_OK) { if (hBinding) FreeBinding(hBinding); delete pArgs; return status; } // Init parts of the RPC_ASYNC_STATE struct that we care about pArgs->async.UserInfo = pArgs; pArgs->async.Event = RpcCallComplete; pArgs->async.NotificationType = RpcNotificationTypeCallback; pArgs->async.u.NotificationRoutine = CProcess::AsyncRpcNotification; // Init other stuff pArgs->dwSig = BINDINGUPDATESTUFF_SIG; pArgs->pProcess = this; pArgs->dwBindingsID = dwBindingsID; pArgs->hBinding = hBinding; // Take a non-client reference (will not stop rundown) on // ourselves, to be owned implicitly by the async call this->Reference(); // Issue async call status = ::UpdateResolverBindings( &(pArgs->async), _hProcess, pdsaResolverBindings, &(pArgs->dwBindingsID), &(pArgs->pdsaNewBindings), &(pArgs->pdsaNewSecurity)); if (status != RPC_S_OK) { // If we get anything other than RPC_S_OK back, that // means we will not receive a call completion notif- // ication. So, we need to cleanup everything up // right here in that case. FreeBinding(hBinding); this->Release(); delete pArgs; } else { DWORD dwAUO = (DWORD)InterlockedIncrement((PLONG)&_dwAsyncUpdatesOutstanding); // This assert is somewhat arbitrary, if it fires there are // one of two things wrong: 1) the machine is so totally // overstressed that the async calls are piling up and are not // getting enough cpu time to complete; or 2) more likely, the // process in question is deadlocked somehow. ASSERT(dwAUO < 5000); } return status; } void CProcess::BindingsUpdateNotify(RPC_BINDING_HANDLE hBinding, DWORD64 dwBindingsID, DUALSTRINGARRAY* pdsaNewBindings, DUALSTRINGARRAY* pdsaSecBindings) /*++ Routine Description: Private helper function. This function is used to process a successful return from an async call to the process to update the bindings. Arguments: hBinding -- binding handle used to make the call. We now own it, either to keep or to cleanup. dwBindingsID -- unique id of the updated bindings. We use this to resolve what to do when async calls arrive\return in an out-of-order fashion. pdsaNewBindings -- new bindings in use by the process. We now own it, either to keep or to cleanup pdsaSecBindings -- new security bindings in use by the process. We now own it, either to keep or to cleanup. Return Value: void --*/ { RPC_STATUS status; CMutexLock callback(&_csCallbackLock); // Always free the binding FreeBinding(hBinding); // Only process the out-params if they contain newer bindings // than what we currently have cached. if ((dwBindingsID > _dwCurrentBindingsID) && pdsaNewBindings && pdsaSecBindings) { // The process has the right bindings, so update our counter // no matter what happens in ProcessBindings. _dwCurrentBindingsID = dwBindingsID; status = ProcessBindings(pdsaNewBindings, pdsaSecBindings); } // Cleanup allocated out-params if (pdsaNewBindings != NULL) MIDL_user_free(pdsaNewBindings); if (pdsaSecBindings != NULL) MIDL_user_free(pdsaSecBindings); InterlockedDecrement((PLONG)&_dwAsyncUpdatesOutstanding); return; } void RPC_ENTRY CProcess::AsyncRpcNotification(RPC_ASYNC_STATE* pAsync, void* pContext, RPC_ASYNC_EVENT Event) /*++ Routine Description: RPC calls this static function when an async call to a server process returns. Arguments: pAsync -- pointer to the async rpc state struct pContext -- rpc thingy, we ignore it Return Value: void --*/ { RPC_STATUS status; HRESULT hrRetVal; BINDINGS_UPDATE_CALLBACK_STUFF* pArgs; pArgs = (BINDINGS_UPDATE_CALLBACK_STUFF*) (((char*)pAsync) - offsetof(BINDINGS_UPDATE_CALLBACK_STUFF, async)); ASSERT(pArgs->async.UserInfo = pArgs); ASSERT(pArgs->dwSig == BINDINGUPDATESTUFF_SIG); ASSERT(pArgs->hBinding); ASSERT(pArgs->pProcess); ASSERT(pArgs->dwBindingsID > 0); // Complete the call. Since we've asked for a direct callback upon // completion, we should never get back RPC_S_ASYNC_CALL_PENDING, so // we assert on this. Otherwise, both RPC and the server need to // return success before we do further processing. status = RpcAsyncCompleteCall(&(pArgs->async), &hrRetVal); ASSERT(status != RPC_S_ASYNC_CALL_PENDING); if ((status == RPC_S_OK) && (hrRetVal == S_OK)) { // Deliver notification to process object. Process may have // already been rundown, that's okay. BindingsUpdateNotify will // own/cleanup the params, see code. pArgs->pProcess->BindingsUpdateNotify(pArgs->hBinding, pArgs->dwBindingsID, pArgs->pdsaNewBindings, pArgs->pdsaNewSecurity); } gpServerLock->LockExclusive(); // This may be the last release on the process object pArgs->pProcess->Release(); gpServerLock->UnlockExclusive(); delete pArgs; return; } CBList *gpProcessList = 0; CProcess * ReferenceProcess( IN PVOID key, IN BOOL fNotContext) /*++ Routine Description: Used to find a CProcess and get a reference on it Arguments: key - The dword key of the process allocated in _Connect. fNotContext - Normally the key is stored as a context handle which means locking is unnecessary. There is an extra refernce which is released buring context rundown which means managers using the key as a context handle a) Don't need to lock the process and b) Don't need to call ReleaseProcess() Return Value: 0 - invalid key non-zero - The process. --*/ { ASSERT(gpProcessList != 0); CProcess *pProcess; gpProcessListLock->LockShared(); if (gpProcessList->Member(key) == FALSE) { gpProcessListLock->UnlockShared(); return(0); } pProcess = (CProcess *)key; if (fNotContext) { pProcess->ClientReference(); } gpProcessListLock->UnlockShared(); return(pProcess); } void ReleaseProcess(CProcess *pProcess) /*++ Routine Description: Releases a pProcess object. This should only be called when a process object has been referenced with the fNotContext == TRUE. Arguments: pProcess - the process to release. May actually be deleted. Return Value: None --*/ { gpProcessListLock->LockExclusive(); if (pProcess->ClientRelease() == 0) { // Process has been completly released the process, // we'll remove it from the list now since we // already have the lock. It may not have been added, // so this may fail. PVOID t = gpProcessList->Remove(pProcess); ASSERT(t == pProcess || t == 0); gpProcessListLock->UnlockExclusive(); // The client process owns one real reference which will be // released in Rundown(). pProcess->Rundown(); } else { gpProcessListLock->UnlockExclusive(); } }