//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information. 
//

/*++



Module Name:

    handlemgr.cpp

Abstract:

    Implementation of a basic handle table



--*/

#include "pal/thread.hpp"
#include "pal/handlemgr.hpp"
#include "pal/cs.hpp"
#include "pal/malloc.hpp"
#include "pal/dbgmsg.h"

using namespace CorUnix;

SET_DEFAULT_DEBUG_CHANNEL(HANDLE);

/* Constants */
/* Special handles */
/* Pseudo handles constant for current thread and process */
const HANDLE hPseudoCurrentProcess = (HANDLE) 0xFFFFFF01;
const HANDLE hPseudoCurrentThread  = (HANDLE) 0xFFFFFF03;
/* Pseudo handle constant for the global IO Completion port */
const HANDLE hPseudoGlobalIOCP  = (HANDLE) 0xFFFFFF05;

PAL_ERROR
CSimpleHandleManager::Initialize(
    void
    )
{
    PAL_ERROR palError = NO_ERROR;
    
    InternalInitializeCriticalSection(&m_csLock);
    m_fLockInitialized = TRUE;

    m_dwTableGrowthRate = c_BasicGrowthRate;

    /* initialize the handle table - the free list is stored in the 'object'
       field, with the head in the global 'm_hiFreeListStart'. */
    m_dwTableSize = m_dwTableGrowthRate;
    
    m_rghteHandleTable = reinterpret_cast<HANDLE_TABLE_ENTRY*>(InternalMalloc((m_dwTableSize * sizeof(HANDLE_TABLE_ENTRY))));
    if(NULL == m_rghteHandleTable)
    {
        ERROR("Unable to create initial handle table array");
        palError = ERROR_OUTOFMEMORY;
        goto InitializeExit;
    }

    for (DWORD i = 0; i < m_dwTableSize; i++)
    {
        m_rghteHandleTable[i].u.hiNextIndex = i + 1;
        m_rghteHandleTable[i].fEntryAllocated = FALSE;
    }

    m_rghteHandleTable[m_dwTableSize - 1].u.hiNextIndex = (HANDLE_INDEX)-1;
    
    m_hiFreeListStart = 0;
    m_hiFreeListEnd = m_dwTableSize - 1;

    TRACE("Handle Manager initialization complete.\n");

InitializeExit:
    
    return palError;
}

PAL_ERROR
CSimpleHandleManager::AllocateHandle(
    CPalThread *pThread,
    IPalObject *pObject,
    DWORD dwAccessRights,
    bool fInheritable,
    HANDLE *ph
    )
{
    PAL_ERROR palError = NO_ERROR;
    DWORD dwIndex;

    Lock(pThread);

    /* if no free handles are available, we need to grow the handle table and
       add new handles to the pool */
    if (m_hiFreeListStart == c_hiInvalid)
    {
        HANDLE_TABLE_ENTRY* rghteTempTable;

        TRACE("Handle pool empty (%d handles allocated), growing handle table "
              "by %d entries.\n", m_dwTableSize, m_dwTableGrowthRate );

        /* make sure handle values don't overflow */
        if (m_dwTableSize + m_dwTableGrowthRate >= c_MaxIndex)
        {
            WARN("Unable to allocate handle : maximum (%d) reached!\n",
                 m_dwTableSize);
            palError = ERROR_OUTOFMEMORY;
            goto AllocateHandleExit;
        }

        /* grow handle table */
        rghteTempTable = reinterpret_cast<HANDLE_TABLE_ENTRY*>(InternalRealloc(
            m_rghteHandleTable,
            (m_dwTableSize + m_dwTableGrowthRate) * sizeof(HANDLE_TABLE_ENTRY)));
        
        if (NULL == rghteTempTable)
        {
            WARN("not enough memory to grow handle table!\n");
            palError = ERROR_OUTOFMEMORY;
            goto AllocateHandleExit;
        }
        m_rghteHandleTable = rghteTempTable;

        /* update handle table and handle pool */
        for (DWORD dw = m_dwTableSize; dw < m_dwTableSize + m_dwTableGrowthRate; dw += 1)
        {
            /* new handles are initially invalid */
            /* the last "old" handle was m_dwTableSize-1, so the new
               handles range from m_dwTableSize to
               m_dwTableSize+m_dwTableGrowthRate-1 */
            m_rghteHandleTable[dw].u.hiNextIndex = dw + 1;
            m_rghteHandleTable[dw].fEntryAllocated = FALSE;
        }

        m_hiFreeListStart = m_dwTableSize;
        m_dwTableSize += m_dwTableGrowthRate;
        m_rghteHandleTable[m_dwTableSize - 1].u.hiNextIndex = (HANDLE_INDEX)-1;
        m_hiFreeListEnd = m_dwTableSize - 1;
        
    }

    /* take the next free handle */
    dwIndex = m_hiFreeListStart;

    /* remove the handle from the pool */
    m_hiFreeListStart = m_rghteHandleTable[dwIndex].u.hiNextIndex;
    
    /* clear the tail record if this is the last handle slot available */
    if(m_hiFreeListStart == c_hiInvalid) 
    {
        m_hiFreeListEnd = c_hiInvalid;
    }

    /* save the data associated with the new handle */
    *ph = HandleIndexToHandle(dwIndex);
    
    pObject->AddReference();
    m_rghteHandleTable[dwIndex].u.pObject = pObject;
    m_rghteHandleTable[dwIndex].dwAccessRights = dwAccessRights;
    m_rghteHandleTable[dwIndex].fInheritable = fInheritable;
    m_rghteHandleTable[dwIndex].fEntryAllocated = TRUE;

AllocateHandleExit:

    Unlock(pThread);

    return palError;    
}

PAL_ERROR
CSimpleHandleManager::GetObjectFromHandle(
    CPalThread *pThread,
    HANDLE h,
    DWORD *pdwRightsGranted,
    IPalObject **ppObject
    )
{
    PAL_ERROR palError = NO_ERROR;
    HANDLE_INDEX hi;

    Lock(pThread);
    
    if (!ValidateHandle(h))
    {
        ERROR("Tried to dereference an invalid handle %p\n", h);
        palError = ERROR_INVALID_HANDLE;
        goto GetObjectFromHandleExit;
    }
    
    hi = HandleToHandleIndex(h);

    *pdwRightsGranted = m_rghteHandleTable[hi].dwAccessRights;
    *ppObject = m_rghteHandleTable[hi].u.pObject;
    (*ppObject)->AddReference();
    
GetObjectFromHandleExit:

    Unlock(pThread);

    return palError;
}

PAL_ERROR
CSimpleHandleManager::FreeHandle(
    CPalThread *pThread,
    HANDLE h
    )
{
    PAL_ERROR palError = NO_ERROR;
    IPalObject *pobj = NULL;
    HANDLE_INDEX hi = HandleToHandleIndex(h);

    Lock(pThread);

    if (!ValidateHandle(h))
    {
        ERROR("Trying to free invalid handle %p.\n", h);
        palError = ERROR_INVALID_HANDLE;
        goto FreeHandleExit;
    }

    if (HandleIsSpecial(h))
    {
        ASSERT("Trying to free Special Handle %p.\n", h);
        palError = ERROR_INVALID_HANDLE;
        goto FreeHandleExit;
    }

    pobj = m_rghteHandleTable[hi].u.pObject;
    m_rghteHandleTable[hi].fEntryAllocated = FALSE;

    /* add handle to the free pool */
    if(m_hiFreeListEnd != c_hiInvalid)
    {
        m_rghteHandleTable[m_hiFreeListEnd].u.hiNextIndex = hi;
    }
    else
    {
        m_hiFreeListStart = hi;
    }
    
    m_rghteHandleTable[hi].u.hiNextIndex = c_hiInvalid;
    m_hiFreeListEnd = hi;

FreeHandleExit:    

    Unlock(pThread);

    if (NULL != pobj)
    {
        pobj->ReleaseReference(pThread);
    }

    return palError;
}

/*++
Function :
    ValidateHandle

    Check if a handle was allocated by this handle manager

Parameters :
    HANDLE handle : handle to check.

Return Value :
    TRUE if valid, FALSE if invalid.
--*/
bool CSimpleHandleManager::ValidateHandle(HANDLE handle)
{
    DWORD dwIndex;
    
    if (NULL == m_rghteHandleTable)
    {
        ASSERT("Handle Manager is not initialized!\n");
        return FALSE;
    }
    
    if (handle == INVALID_HANDLE_VALUE || handle == 0)
    {
        TRACE( "INVALID_HANDLE_VALUE or NULL value is not a valid handle.\n" );
        return FALSE;
    }

    if (HandleIsSpecial(handle))
    {
        //
        // Special handles are valid in the general sense. They are not valid
        // in this context, though, as they were not allocated by the handle
        // manager. Hitting this case indicates a logic error within the PAL
        // (since clients of the handle manager should have already dealt with
        // the specialness of the handle) so we assert here.
        //
        
        ASSERT ("Handle %p is a special handle, returning FALSE.\n", handle);
        return FALSE;
    }

    dwIndex = HandleToHandleIndex(handle);

    if (dwIndex >= m_dwTableSize)
    {
        WARN( "The handle value(%p) is out of the bounds for the handle table.\n", handle );
        return FALSE;
    }

    if (!m_rghteHandleTable[dwIndex].fEntryAllocated)
    {
        WARN("The handle value (%p) has not been allocated\n", handle);
        return FALSE;
    }

    return TRUE;
}

bool
CorUnix::HandleIsSpecial(
    HANDLE h
    )
{
    return (hPseudoCurrentProcess == h ||
            hPseudoCurrentThread == h ||
            hPseudoGlobalIOCP == h);
}

