//============================================================================ // Copyright (c) 2000, Microsoft Corporation // // File: mapper.c // // History: // Yi Sun June-27-2000 Created // // Abstract: // We implement a locking system with read and write locks. Callers // can simply acquire a read lock to the obj to prevent the obj // from being released since whoever doing the release is supposed // acquire the write lock first. Routines for mapping between // obj pointers and handles are also provided. //============================================================================ #include "nt.h" #include "ntrtl.h" #include "nturtl.h" #include "windows.h" #include "tapi.h" #include "ndptsp.h" typedef struct _RW_LOCK { CRITICAL_SECTION critSec; // critical section HANDLE hEvent; // no-one-holds-any-lock event DWORD dwRefCt; // number of threads holding locks } RW_LOCK, *PRW_LOCK; typedef struct _MAPPER_ENTRY { // DEF: a free entry is one that no obj is associated with RW_LOCK rwLock; // a lock for each entry to ensure thread-safe PVOID pObjPtr; // point to the mem block of the associated obj // NULL when the entry is free FREEOBJPROC pfnFreeProc; // function to call to free the obj WORD wID; // id used for detecting bad handles // valid value range: 1 - 0x7FFF WORD wIndexNextFree; // index of the next free entry in the global // mapper array, invalid when the entry is busy } MAPPER_ENTRY, *PMAPPER_ENTRY; typedef struct _HANDLE_OBJECT_MAPPER { RW_LOCK rwLock; // a global lock for the whole mapper WORD wNextID; // a global id counter incremented // after each handle mapping WORD wIndexFreeHead; // index of head of free entry list DWORD dwCapacity; // total number of entries in the array DWORD dwFree; // total number of free entries left PMAPPER_ENTRY pArray; // the global array that keeps all the mapping } HANDLE_OBJECT_MAPPER; // the capacity to begin with, can be read from registry #define INITIAL_MAPPER_SIZE 32 #define MAXIMUM_MAPPER_SIZE (64 * 1024) // 16-bit index limitation // the global mapper object static HANDLE_OBJECT_MAPPER gMapper; BOOL InitializeRWLock( IN PRW_LOCK pLock ) { // create an autoreset event, non-signaled initially pLock->hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); if (NULL == pLock->hEvent) { return FALSE; } InitializeCriticalSection(&pLock->critSec); pLock->dwRefCt = 0; return TRUE; } BOOL UninitializeRWLock( IN PRW_LOCK pLock ) { pLock->dwRefCt = 0; DeleteCriticalSection(&pLock->critSec); return CloseHandle(pLock->hEvent); } // // NOTE: due to the limitation of the current // implementation, calling AcquireWriteLock() // while holding a read lock of the same // RW_LOCK will result in a DEADLOCK!!! // Be sure to release the read lock before // attempting to acquire the write lock. // This limitation can be lifted by implementing // lock upgrade (from read to write) which // requires RW_LOCK to remember ids of all // owning threads. // VOID AcquireReadLock( IN PRW_LOCK pLock ) { // // increase the ref count, then leave // the critical section to allow others // to enter // EnterCriticalSection(&pLock->critSec); ++pLock->dwRefCt; LeaveCriticalSection(&pLock->critSec); } VOID ReleaseReadLock( IN PRW_LOCK pLock ) { // // decrease the ref count, check whether // the new ref count is 0 (meaning no one // else holds any lock), if yes, signal // the event to allow others waiting to // acquire write locks to continue // EnterCriticalSection(&pLock->critSec); if (0 == --pLock->dwRefCt) { SetEvent(pLock->hEvent); } LeaveCriticalSection(&pLock->critSec); } VOID AcquireWriteLock( IN PRW_LOCK pLock ) { // // enter critical section, check whether // the ref count is 0: if yes, return // without leaving the critical section // to block others from entering; if no, // leave the section before wait for others // to release locks then reenter the section // try_entering_crit_sec: EnterCriticalSection(&pLock->critSec); if (pLock->dwRefCt > 0) { // make sure leaving critSec before waiting LeaveCriticalSection(&pLock->critSec); WaitForSingleObject(pLock->hEvent, INFINITE); goto try_entering_crit_sec; } pLock->dwRefCt = 1; } VOID ReleaseWriteLock( IN PRW_LOCK pLock ) { // // reset the ref count to 0, signal // the event, leave the critical section // pLock->dwRefCt = 0; SetEvent(pLock->hEvent); LeaveCriticalSection(&pLock->critSec); } LONG InitializeMapper( ) { DWORD dwIndex; TspLog(DL_TRACE, "InitializeMapper: entering..."); // alloc and zeroinit the array gMapper.pArray = (PMAPPER_ENTRY) MALLOC(INITIAL_MAPPER_SIZE * sizeof(MAPPER_ENTRY)); if (NULL == gMapper.pArray) { TspLog(DL_ERROR, "InitializeMapper: failed to alloc(1) mapper array"); return LINEERR_NOMEM; } // init the global lock for the mapper InitializeRWLock(&gMapper.rwLock); gMapper.wNextID = 1; gMapper.wIndexFreeHead = 0; gMapper.dwCapacity = INITIAL_MAPPER_SIZE; gMapper.dwFree = INITIAL_MAPPER_SIZE; // init the lock for each mapper entry and link the free entry list for (dwIndex = 0; dwIndex < INITIAL_MAPPER_SIZE - 1; dwIndex++) { InitializeRWLock(&(gMapper.pArray[dwIndex].rwLock)); gMapper.pArray[dwIndex].wIndexNextFree = (WORD)(dwIndex + 1); } InitializeRWLock(&(gMapper.pArray[INITIAL_MAPPER_SIZE - 1].rwLock)); return TAPI_SUCCESS; } VOID UninitializeMapper() { DWORD dwIndex; for (dwIndex = 0; dwIndex < gMapper.dwCapacity; dwIndex++) { UninitializeRWLock(&(gMapper.pArray[dwIndex].rwLock)); } UninitializeRWLock(&gMapper.rwLock); FREE(gMapper.pArray); TspLog(DL_TRACE, "UninitializeMapper: exited"); } // // NOTE: both OpenObjHandle() and CloseObjHandle() acquire write lock of // gMapper.rwLock at the beginning and release it at the end; // but that's not the case for AcquireObjReadLock(), GetObjWithReadLock(), // AcquireObjWriteLock() and GetObjWithWriteLock(): they acquire read // lock of gMapper.rwLock at the beginning and never release it before // exit, the lock is actually released in either ReleaseObjReadLock() // or ReleaseObjWriteLock(), which means the caller thread of these // four lock-acquiring functions actually not only holds the lock // it intends to acquire but also holds the read lock of gMapper.rwLock // as a by-product. The reason for that is preventing CloseObjHandle() // from getting the write lock of gMapper.rwLock while waiting for the // write lock for a mapper entry -- that sure will result in a DEADLOCK // because if another thread has the read lock for that entry, for it to // release the lock, it needs to acquire the read lock of gMapper.rwLock. // The consequence of keeping the read lock of gMapper.rwLock is that // the caller thread has to call ReleaseObjXXXLock() to release it // before calling OpenObjHandle() or CloseObjHandle() to avoid another // kind of DEADLOCK (see previous NOTE). // LONG OpenObjHandle( IN PVOID pObjPtr, IN FREEOBJPROC pfnFreeProc, OUT HANDLE *phObj ) { WORD wIndex; PMAPPER_ENTRY pEntry; DWORD dwHandle; AcquireWriteLock(&gMapper.rwLock); if (0 == gMapper.dwFree) { DWORD dwIndex; DWORD dwOldSize = gMapper.dwCapacity; PMAPPER_ENTRY pOldArray = gMapper.pArray; if (MAXIMUM_MAPPER_SIZE == gMapper.dwCapacity) { TspLog(DL_ERROR, "OpenObjHandle: failed to grow mapper array"); ReleaseWriteLock(&gMapper.rwLock); return LINEERR_OPERATIONFAILED; } // increase the capacity by a factor of two gMapper.dwCapacity <<= 1; // allocate a new array twice the old size, then zeroinit it gMapper.pArray = (PMAPPER_ENTRY) MALLOC(gMapper.dwCapacity * sizeof(MAPPER_ENTRY)); if (NULL == gMapper.pArray) { TspLog(DL_ERROR, "OpenObjHandle: failed to alloc(2) mapper array"); ReleaseWriteLock(&gMapper.rwLock); return LINEERR_NOMEM; } TspLog(DL_INFO, "OpenObjHandle: the mapper array has grown to %d", gMapper.dwCapacity); // copy the old array over for (dwIndex = 0; dwIndex < dwOldSize; dwIndex++) { CopyMemory(&(gMapper.pArray[dwIndex].rwLock), &(pOldArray[dwIndex].rwLock), sizeof(RW_LOCK)); // // Delete the lock from the old table and initialize // the cs in the new table. Otherwise pageheap will // assert when oldtable is being freed - and its not // a good thing anyway. Note that since the global // lock is held across all Acquire/Get/Release functions // for the lock, this is a safe operation to do here - // no object would be holding the lock when this is // being done since we are holding the write lock // for the gmapper. // DeleteCriticalSection(&pOldArray[dwIndex].rwLock.critSec); InitializeCriticalSection(&gMapper.pArray[dwIndex].rwLock.critSec); gMapper.pArray[dwIndex].pObjPtr = pOldArray[dwIndex].pObjPtr; gMapper.pArray[dwIndex].pfnFreeProc = pOldArray[dwIndex].pfnFreeProc; gMapper.pArray[dwIndex].wID = pOldArray[dwIndex].wID; } // init locks for new entries and link them for (dwIndex = dwOldSize; dwIndex < gMapper.dwCapacity - 1; dwIndex++) { InitializeRWLock(&(gMapper.pArray[dwIndex].rwLock)); gMapper.pArray[dwIndex].wIndexNextFree = (WORD)(dwIndex + 1); } InitializeRWLock(&(gMapper.pArray[gMapper.dwCapacity - 1].rwLock)); // reset the globals gMapper.dwFree = dwOldSize; gMapper.wIndexFreeHead = (WORD)dwOldSize; // free the old array FREE(pOldArray); } ASSERT(gMapper.dwFree != 0); wIndex = gMapper.wIndexFreeHead; pEntry = gMapper.pArray + wIndex; gMapper.wIndexFreeHead = pEntry->wIndexNextFree; gMapper.dwFree--; pEntry->pObjPtr = pObjPtr; pEntry->pfnFreeProc = pfnFreeProc; pEntry->wID = gMapper.wNextID++; // make sure wNextID is within range if (gMapper.wNextID & 0x8000) { gMapper.wNextID = 1; } pEntry->wIndexNextFree = 0; // it's always 0 when the entry is not free // // bit 0 is always 0 // bits 1-16 contains the index into pArray // bits 17-31 contains the id // // this enables us to differentiate the TSP handles // created here for outgoing calls and the pseudo handles // created in NDPROXY for incoming calls which always // has the lower bit set // dwHandle = (((pEntry->wID) << 16) | wIndex) << 1; // a handle is a ptr, so on 64-bit platform, dwHandle needs to be extended *phObj = (HANDLE)UlongToPtr(dwHandle); ReleaseWriteLock(&gMapper.rwLock); return TAPI_SUCCESS; } LONG CloseObjHandle( IN HANDLE hObj ) { DWORD dwHandle = PtrToUlong(hObj) >> 1; WORD wIndex = (WORD)(dwHandle & 0xFFFF); WORD wID = (WORD)(dwHandle >> 16); AcquireWriteLock(&gMapper.rwLock); if ((wIndex >= gMapper.dwCapacity) || (wID != gMapper.pArray[wIndex].wID) || (NULL == gMapper.pArray[wIndex].pObjPtr)) { TspLog(DL_WARNING, "CloseObjHandle: bad handle(%p)", hObj); ReleaseWriteLock(&gMapper.rwLock); return LINEERR_OPERATIONFAILED; } AcquireWriteLock(&gMapper.pArray[wIndex].rwLock); #if DBG TspLog(DL_TRACE, "CloseObjHandle: closing handle(%p)", hObj); #endif //DBG // free the obj (*(gMapper.pArray[wIndex].pfnFreeProc))(gMapper.pArray[wIndex].pObjPtr); // close obj handle gMapper.pArray[wIndex].pObjPtr = NULL; gMapper.pArray[wIndex].pfnFreeProc = NULL; gMapper.pArray[wIndex].wID = 0; // insert the entry into the free list as the head gMapper.pArray[wIndex].wIndexNextFree = gMapper.wIndexFreeHead; gMapper.wIndexFreeHead = wIndex; // update the free total gMapper.dwFree++; ReleaseWriteLock(&gMapper.pArray[wIndex].rwLock); ReleaseWriteLock(&gMapper.rwLock); return TAPI_SUCCESS; } LONG AcquireObjReadLock( IN HANDLE hObj ) { DWORD dwHandle = PtrToUlong(hObj) >> 1; WORD wIndex = (WORD)(dwHandle & 0xFFFF); WORD wID = (WORD)(dwHandle >> 16); AcquireReadLock(&gMapper.rwLock); if ((wIndex >= gMapper.dwCapacity) || (wID != gMapper.pArray[wIndex].wID) || (NULL == gMapper.pArray[wIndex].pObjPtr)) { TspLog(DL_WARNING, "AcquireObjReadLock: bad handle(%p)", hObj); ReleaseReadLock(&gMapper.rwLock); return LINEERR_OPERATIONFAILED; } AcquireReadLock(&gMapper.pArray[wIndex].rwLock); #if DBG TspLog(DL_TRACE, "AcquireObjReadLock: RefCt(%p, %d)", hObj, gMapper.pArray[wIndex].rwLock.dwRefCt); #endif //DBG return TAPI_SUCCESS; } LONG GetObjWithReadLock( IN HANDLE hObj, OUT PVOID *ppObjPtr ) { DWORD dwHandle = PtrToUlong(hObj) >> 1; WORD wIndex = (WORD)(dwHandle & 0xFFFF); WORD wID = (WORD)(dwHandle >> 16); AcquireReadLock(&gMapper.rwLock); if ((wIndex >= gMapper.dwCapacity) || (wID != gMapper.pArray[wIndex].wID) || (NULL == gMapper.pArray[wIndex].pObjPtr)) { TspLog(DL_WARNING, "GetObjWithReadLock: bad handle(%p)", hObj); ReleaseReadLock(&gMapper.rwLock); return LINEERR_OPERATIONFAILED; } AcquireReadLock(&gMapper.pArray[wIndex].rwLock); #if DBG TspLog(DL_TRACE, "GetObjWithReadLock: RefCt(%p, %d)", hObj, gMapper.pArray[wIndex].rwLock.dwRefCt); #endif //DBG *ppObjPtr = gMapper.pArray[wIndex].pObjPtr; return TAPI_SUCCESS; } LONG ReleaseObjReadLock( IN HANDLE hObj ) { DWORD dwHandle = PtrToUlong(hObj) >> 1; WORD wIndex = (WORD)(dwHandle & 0xFFFF); WORD wID = (WORD)(dwHandle >> 16); if ((wIndex >= gMapper.dwCapacity) || (wID != gMapper.pArray[wIndex].wID) || (NULL == gMapper.pArray[wIndex].pObjPtr)) { TspLog(DL_WARNING, "ReleaseObjReadLock: bad handle(%p)", hObj); return LINEERR_OPERATIONFAILED; } ReleaseReadLock(&gMapper.pArray[wIndex].rwLock); #if DBG TspLog(DL_TRACE, "ReleaseObjReadLock: RefCt(%p, %d)", hObj, gMapper.pArray[wIndex].rwLock.dwRefCt); #endif //DBG ReleaseReadLock(&gMapper.rwLock); return TAPI_SUCCESS; } LONG AcquireObjWriteLock( IN HANDLE hObj ) { DWORD dwHandle = PtrToUlong(hObj) >> 1; WORD wIndex = (WORD)(dwHandle & 0xFFFF); WORD wID = (WORD)(dwHandle >> 16); AcquireReadLock(&gMapper.rwLock); if ((wIndex >= gMapper.dwCapacity) || (wID != gMapper.pArray[wIndex].wID) || (NULL == gMapper.pArray[wIndex].pObjPtr)) { TspLog(DL_WARNING, "AcquireObjWriteLock: bad handle(%p)", hObj); ReleaseReadLock(&gMapper.rwLock); return LINEERR_OPERATIONFAILED; } AcquireWriteLock(&gMapper.pArray[wIndex].rwLock); #if DBG TspLog(DL_TRACE, "AcquireObjWriteLock: RefCt(%p, %d)", hObj, gMapper.pArray[wIndex].rwLock.dwRefCt); #endif //DBG return TAPI_SUCCESS; } LONG GetObjWithWriteLock( IN HANDLE hObj, OUT PVOID *ppObjPtr ) { DWORD dwHandle = PtrToUlong(hObj) >> 1; WORD wIndex = (WORD)(dwHandle & 0xFFFF); WORD wID = (WORD)(dwHandle >> 16); AcquireReadLock(&gMapper.rwLock); if ((wIndex >= gMapper.dwCapacity) || (wID != gMapper.pArray[wIndex].wID) || (NULL == gMapper.pArray[wIndex].pObjPtr)) { TspLog(DL_WARNING, "GetObjWithWriteLock: bad handle(%p)", hObj); ReleaseReadLock(&gMapper.rwLock); return LINEERR_OPERATIONFAILED; } AcquireWriteLock(&gMapper.pArray[wIndex].rwLock); #if DBG TspLog(DL_TRACE, "GetObjWithWriteLock: RefCt(%p, %d)", hObj, gMapper.pArray[wIndex].rwLock.dwRefCt); #endif //DBG *ppObjPtr = gMapper.pArray[wIndex].pObjPtr; return TAPI_SUCCESS; } LONG ReleaseObjWriteLock( IN HANDLE hObj ) { DWORD dwHandle = PtrToUlong(hObj) >> 1; WORD wIndex = (WORD)(dwHandle & 0xFFFF); WORD wID = (WORD)(dwHandle >> 16); if ((wIndex >= gMapper.dwCapacity) || (wID != gMapper.pArray[wIndex].wID) || (NULL == gMapper.pArray[wIndex].pObjPtr)) { TspLog(DL_WARNING, "ReleaseObjWriteLock: bad handle(%p)", hObj); return LINEERR_OPERATIONFAILED; } ReleaseWriteLock(&gMapper.pArray[wIndex].rwLock); #if DBG TspLog(DL_TRACE, "ReleaseObjWriteLock: RefCt(%p, %d)", hObj, gMapper.pArray[wIndex].rwLock.dwRefCt); #endif //DBG ReleaseReadLock(&gMapper.rwLock); return TAPI_SUCCESS; }