/*
 * Handle Tables
 *
 * Copyright (C) 2004 Robert Shearman
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include <stdarg.h>

#include "ntstatus.h"
#define WIN32_NO_STATUS
#include "windef.h"
#include "winternl.h"
#include "wine/debug.h"
#include "ntdll_misc.h"

WINE_DEFAULT_DEBUG_CHANNEL(ntdll);

/**************************************************************************
 *	RtlInitializeHandleTable   (NTDLL.@)
 *
 * Initializes a handle table.
 *
 * PARAMS
 *  MaxHandleCount [I] The maximum number of handles the handle table will support.
 *  HandleSize     [I] The size of each handle.
 *  HandleTable    [I/O] The handle table.
 *
 * RETURNS
 *  Nothing.
 *
 * SEE
 *  RtlDestroyHandleTable().
 */
void WINAPI RtlInitializeHandleTable(ULONG MaxHandleCount, ULONG HandleSize, RTL_HANDLE_TABLE * HandleTable)
{
    TRACE("(%u, %u, %p)\n", MaxHandleCount, HandleSize, HandleTable);

    memset(HandleTable, 0, sizeof(*HandleTable));
    HandleTable->MaxHandleCount = MaxHandleCount;
    HandleTable->HandleSize = HandleSize;
}

/**************************************************************************
 *	RtlDestroyHandleTable   (NTDLL.@)
 *
 * Destroys a handle table and frees associated resources.
 *
 * PARAMS
 *  HandleTable    [I] The handle table.
 *
 * RETURNS
 *  Any status code returned by NtFreeVirtualMemory().
 *
 * NOTES
 *  The native version of this API doesn't free the virtual memory that has
 *  been previously reserved, only the committed memory. There is no harm
 *  in also freeing the reserved memory because it won't have been handed out
 *  to any callers. I believe it is "more polite" to free everything.
 *
 * SEE
 *  RtlInitializeHandleTable().
 */
NTSTATUS WINAPI RtlDestroyHandleTable(RTL_HANDLE_TABLE * HandleTable)
{
    SIZE_T Size = 0;

    TRACE("(%p)\n", HandleTable);

    /* native version only releases committed memory, but we also release reserved */
    return NtFreeVirtualMemory(
        NtCurrentProcess(),
        &HandleTable->FirstHandle,
        &Size,
        MEM_RELEASE);
}

/**************************************************************************
 *	RtlpAllocateSomeHandles   (internal)
 *
 * Reserves memory for the handles if not previously done and commits memory
 * for a batch of handles if none are free and adds them to the free list.
 *
 * PARAMS
 *  HandleTable    [I/O] The handle table.
 *
 * RETURNS
 *  NTSTATUS code.
 */
static NTSTATUS RtlpAllocateSomeHandles(RTL_HANDLE_TABLE * HandleTable)
{
    NTSTATUS status;
    if (!HandleTable->FirstHandle)
    {
        PVOID FirstHandleAddr = NULL;
        SIZE_T MaxSize = HandleTable->MaxHandleCount * HandleTable->HandleSize;

        /* reserve memory for the handles, but don't commit it yet because we
         * probably won't use most of it and it will use up physical memory */
        status = NtAllocateVirtualMemory(
            NtCurrentProcess(),
            &FirstHandleAddr,
            0,
            &MaxSize,
            MEM_RESERVE,
            PAGE_READWRITE);
        if (status != STATUS_SUCCESS)
            return status;
        HandleTable->FirstHandle = FirstHandleAddr;
        HandleTable->ReservedMemory = HandleTable->FirstHandle;
        HandleTable->MaxHandle = (char *)HandleTable->FirstHandle + MaxSize;
    }
    if (!HandleTable->NextFree)
    {
        SIZE_T Offset, CommitSize = 4096; /* one page */
        RTL_HANDLE * FreeHandle = NULL;
        PVOID NextAvailAddr = HandleTable->ReservedMemory;

        if (HandleTable->ReservedMemory >= HandleTable->MaxHandle)
            return STATUS_NO_MEMORY; /* the handle table is completely full */

        status = NtAllocateVirtualMemory(
            NtCurrentProcess(),
            &NextAvailAddr,
            0,
            &CommitSize,
            MEM_COMMIT,
            PAGE_READWRITE);
        if (status != STATUS_SUCCESS)
            return status;

        for (Offset = 0; Offset < CommitSize; Offset += HandleTable->HandleSize)
        {
            /* make sure we don't go over handle limit, even if we can
             * because of rounding of the table size up to the next page
             * boundary */
            if ((char *)HandleTable->ReservedMemory + Offset >= (char *)HandleTable->MaxHandle)
                break;

            FreeHandle = (RTL_HANDLE *)((char *)HandleTable->ReservedMemory + Offset);

            FreeHandle->Next = (RTL_HANDLE *)((char *)HandleTable->ReservedMemory + 
                Offset + HandleTable->HandleSize);
        }

        /* shouldn't happen because we already test for this above, but
         * handle it just in case */
        if (!FreeHandle)
            return STATUS_NO_MEMORY;

        /* set the last handle's Next pointer to NULL so that when we run
         * out of free handles we trigger another commit of memory and
         * initialize the free pointers */
        FreeHandle->Next = NULL;

        HandleTable->NextFree = HandleTable->ReservedMemory;

        HandleTable->ReservedMemory = (char *)HandleTable->ReservedMemory + CommitSize;
    }
    return STATUS_SUCCESS;
}

/**************************************************************************
 *	RtlAllocateHandle   (NTDLL.@)
 *
 * Allocates a handle from the handle table.
 *
 * PARAMS
 *  HandleTable    [I/O] The handle table.
 *  HandleIndex    [O] Index of the handle returned. Optional.
 *
 * RETURNS
 *  Success: Pointer to allocated handle.
 *  Failure: NULL.
 *
 * NOTES
 *  A valid handle must have the bit set as indicated in the code below 
 *  otherwise subsequent RtlIsValidHandle() calls will fail.
 *
 *  static inline void RtlpMakeHandleAllocated(RTL_HANDLE * Handle)
 *  {
 *    ULONG_PTR *AllocatedBit = (ULONG_PTR *)(&Handle->Next);
 *    *AllocatedBit = *AllocatedBit | 1;
 *  }
 *
 * SEE
 *  RtlFreeHandle().
 */
RTL_HANDLE * WINAPI RtlAllocateHandle(RTL_HANDLE_TABLE * HandleTable, ULONG * HandleIndex)
{
    RTL_HANDLE * ret;

    TRACE("(%p, %p)\n", HandleTable, HandleIndex);

    if (!HandleTable->NextFree && RtlpAllocateSomeHandles(HandleTable) != STATUS_SUCCESS)
        return NULL;

    ret = HandleTable->NextFree;
    HandleTable->NextFree = ret->Next;

    if (HandleIndex)
        *HandleIndex = (ULONG)(((PCHAR)ret - (PCHAR)HandleTable->FirstHandle) / HandleTable->HandleSize);

    return ret;
}

/**************************************************************************
 *	RtlFreeHandle   (NTDLL.@)
 *
 * Frees an allocated handle.
 *
 * PARAMS
 *  HandleTable    [I/O] The handle table.
 *  Handle         [I] The handle to be freed.
 *
 * RETURNS
 *  Success: TRUE.
 *  Failure: FALSE.
 *
 * SEE
 *  RtlAllocateHandle().
 */
BOOLEAN WINAPI RtlFreeHandle(RTL_HANDLE_TABLE * HandleTable, RTL_HANDLE * Handle)
{
    TRACE("(%p, %p)\n", HandleTable, Handle);
    /* NOTE: we don't validate the handle and we don't make Handle->Next even
     * again to signal that it is no longer in user - that is done as a side
     * effect of setting Handle->Next to the previously next free handle in
     * the handle table */
    memset(Handle, 0, HandleTable->HandleSize);
    Handle->Next = HandleTable->NextFree;
    HandleTable->NextFree = Handle;
    return TRUE;
}

/**************************************************************************
 *	RtlIsValidHandle   (NTDLL.@)
 *
 * Determines whether a handle is valid or not.
 *
 * PARAMS
 *  HandleTable    [I] The handle table.
 *  Handle         [I] The handle to be tested.
 *
 * RETURNS
 *  Valid: TRUE.
 *  Invalid: FALSE.
 */
BOOLEAN WINAPI RtlIsValidHandle(const RTL_HANDLE_TABLE * HandleTable, const RTL_HANDLE * Handle)
{
    TRACE("(%p, %p)\n", HandleTable, Handle);
    /* make sure handle is within used region and that it is aligned on
     * a HandleTable->HandleSize boundary and that Handle->Next is odd,
     * indicating that the handle is active */
    if ((Handle >= (RTL_HANDLE *)HandleTable->FirstHandle) &&
      (Handle < (RTL_HANDLE *)HandleTable->ReservedMemory) &&
      !((ULONG_PTR)Handle & (HandleTable->HandleSize - 1)) &&
      ((ULONG_PTR)Handle->Next & 1))
        return TRUE;
    else
        return FALSE;
}

/**************************************************************************
 *	RtlIsValidIndexHandle   (NTDLL.@)
 *
 * Determines whether a handle index is valid or not.
 *
 * PARAMS
 *  HandleTable    [I] The handle table.
 *  Index          [I] The index of the handle to be tested.
 *  ValidHandle    [O] The handle Index refers to.
 *
 * RETURNS
 *  Valid: TRUE.
 *  Invalid: FALSE.
 */
BOOLEAN WINAPI RtlIsValidIndexHandle(const RTL_HANDLE_TABLE * HandleTable, ULONG Index, RTL_HANDLE ** ValidHandle)
{
    RTL_HANDLE * Handle;

    TRACE("(%p, %u, %p)\n", HandleTable, Index, ValidHandle);
    Handle = (RTL_HANDLE *)
        ((char *)HandleTable->FirstHandle + Index * HandleTable->HandleSize);

    if (RtlIsValidHandle(HandleTable, Handle))
    {
        *ValidHandle = Handle;
        return TRUE;
    }
    return FALSE;
}