/*
 * Win32 process handles
 *
 * Copyright 1998 Alexandre Julliard
 */

#include <assert.h>
#include <stdio.h>
#include "winbase.h"
#include "winerror.h"
#include "heap.h"
#include "process.h"
#include "server.h"
#include "thread.h"
#include "debug.h"

#define HTABLE_SIZE  0x30  /* Handle table initial size */
#define HTABLE_INC   0x10  /* Handle table increment */

/* Reserved access rights */
#define RESERVED_ALL           (0x0007 << RESERVED_SHIFT)
#define RESERVED_SHIFT         25
#define RESERVED_INHERIT       (HANDLE_FLAG_INHERIT<<RESERVED_SHIFT)
#define RESERVED_CLOSE_PROTECT (HANDLE_FLAG_PROTECT_FROM_CLOSE<<RESERVED_SHIFT)


/***********************************************************************
 *           HANDLE_GrowTable
 */
static BOOL32 HANDLE_GrowTable( PDB32 *process, INT32 incr )
{
    HANDLE_TABLE *table;

    SYSTEM_LOCK();
    table = process->handle_table;
    table = HeapReAlloc( process->system_heap,
                         HEAP_ZERO_MEMORY | HEAP_NO_SERIALIZE, table,
                         sizeof(HANDLE_TABLE) +
                         (table->count + incr - 1) * sizeof(HANDLE_ENTRY) );
    if (table)
    {
        table->count += incr;
        process->handle_table = table;
    }
    SYSTEM_UNLOCK();
    return (table != NULL);
}


/***********************************************************************
 *           HANDLE_CreateTable
 *
 * Create a process handle table, optionally inheriting the parent's handles.
 */
BOOL32 HANDLE_CreateTable( PDB32 *pdb, BOOL32 inherit )
{
    DWORD size;

    /* Process must not already have a handle table */
    assert( !pdb->handle_table );

    /* If this is the first process, simply allocate a table */
    if (!pdb->parent) inherit = FALSE;

    SYSTEM_LOCK();
    size = inherit ? pdb->parent->handle_table->count : HTABLE_SIZE;
    if ((pdb->handle_table = HeapAlloc( pdb->system_heap,
                                        HEAP_ZERO_MEMORY | HEAP_NO_SERIALIZE,
                                        sizeof(HANDLE_TABLE) +
                                        (size-1) * sizeof(HANDLE_ENTRY) )))
    {
        pdb->handle_table->count = size;
        if (inherit)
        {
            HANDLE_ENTRY *src = pdb->parent->handle_table->entries;
            HANDLE_ENTRY *dst = pdb->handle_table->entries;
            HANDLE32 h;

            for (h = 0; h < size; h++, src++, dst++)
            {
                /* Check if handle is valid and inheritable */
                if (src->ptr && (src->access & RESERVED_INHERIT))
                {
                    dst->access = src->access;
                    dst->ptr    = src->ptr;
                    dst->server = src->server;
                    K32OBJ_IncCount( dst->ptr );
                }
            }
        }
        /* Handle 1 is the process itself (unless the parent decided otherwise) */
        if (!pdb->handle_table->entries[1].ptr)
        {
            pdb->handle_table->entries[1].ptr    = &pdb->header;
            pdb->handle_table->entries[1].access = PROCESS_ALL_ACCESS;
            pdb->handle_table->entries[1].server = -1;  /* FIXME */
            K32OBJ_IncCount( &pdb->header );
        }
    }
    SYSTEM_UNLOCK();
    return (pdb->handle_table != NULL);
}


/***********************************************************************
 *           HANDLE_Alloc
 *
 * Allocate a handle for a kernel object and increment its refcount.
 */
HANDLE32 HANDLE_Alloc( PDB32 *pdb, K32OBJ *ptr, DWORD access,
                       BOOL32 inherit, int server_handle )
{
    HANDLE32 h;
    HANDLE_ENTRY *entry;

    assert( ptr );

    /* Set the inherit reserved flag */
    access &= ~RESERVED_ALL;
    if (inherit) access |= RESERVED_INHERIT;

    SYSTEM_LOCK();
    K32OBJ_IncCount( ptr );
    /* Don't try to allocate handle 0 */
    entry = pdb->handle_table->entries + 1;
    for (h = 1; h < pdb->handle_table->count; h++, entry++)
        if (!entry->ptr) break;
    if ((h < pdb->handle_table->count) || HANDLE_GrowTable( pdb, HTABLE_INC ))
    {
        entry = &pdb->handle_table->entries[h];
        entry->access = access;
        entry->ptr    = ptr;
        entry->server = server_handle;
        SYSTEM_UNLOCK();
        return h;
    }
    K32OBJ_DecCount( ptr );
    SYSTEM_UNLOCK();
    if (server_handle != -1) CLIENT_CloseHandle( server_handle );
    SetLastError( ERROR_OUTOFMEMORY );
    return INVALID_HANDLE_VALUE32;
}


/***********************************************************************
 *           HANDLE_GetObjPtr
 *
 * Retrieve a pointer to a kernel object and increments its reference count.
 * The refcount must be decremented when the pointer is no longer used.
 */
K32OBJ *HANDLE_GetObjPtr( PDB32 *pdb, HANDLE32 handle,
                          K32OBJ_TYPE type, DWORD access,
                          int *server_handle )
{
    K32OBJ *ptr = NULL;

    SYSTEM_LOCK();
    if (HANDLE_IS_GLOBAL( handle ))
    {
        handle = HANDLE_GLOBAL_TO_LOCAL( handle );
        pdb = PROCESS_Initial();
    }
    if ((handle > 0) && (handle < pdb->handle_table->count))
    {
        HANDLE_ENTRY *entry = &pdb->handle_table->entries[handle];
        if ((entry->access & access) != access)
            WARN(win32, "Handle %08x bad access (acc=%08lx req=%08lx)\n",
                     handle, entry->access, access );
        ptr = entry->ptr;
        if (server_handle) *server_handle = entry->server;
    }
    else if (handle == CURRENT_THREAD_PSEUDOHANDLE)
    {
       ptr = (K32OBJ *)THREAD_Current();
       if (server_handle) *server_handle = CURRENT_THREAD_PSEUDOHANDLE;
    }
    else if (handle == CURRENT_PROCESS_PSEUDOHANDLE)
    {
       ptr = (K32OBJ *)PROCESS_Current();
       if (server_handle) *server_handle = CURRENT_PROCESS_PSEUDOHANDLE;
    }

    if (ptr && ((type == K32OBJ_UNKNOWN) || (ptr->type == type)))
        K32OBJ_IncCount( ptr );
    else
        ptr = NULL;

    SYSTEM_UNLOCK();
    if (!ptr) SetLastError( ERROR_INVALID_HANDLE );
    return ptr;
}


/***********************************************************************
 *           HANDLE_GetServerHandle
 *
 * Retrieve the server handle associated to an object.
 */
int HANDLE_GetServerHandle( PDB32 *pdb, HANDLE32 handle,
                            K32OBJ_TYPE type, DWORD access )
{
    int server_handle;
    K32OBJ *obj;

    SYSTEM_LOCK();
    if ((obj = HANDLE_GetObjPtr( pdb, handle, type, access, &server_handle )))
        K32OBJ_DecCount( obj );
    else
        server_handle = -1;
    SYSTEM_UNLOCK();
    return server_handle;
}


/*********************************************************************
 *           HANDLE_GetAccess
 */
static BOOL32 HANDLE_GetAccess( PDB32 *pdb, HANDLE32 handle, LPDWORD access )
{
    BOOL32 ret = FALSE;

    SYSTEM_LOCK();
    if ((handle > 0) && (handle < pdb->handle_table->count))
    {
        HANDLE_ENTRY *entry = &pdb->handle_table->entries[handle];
        if (entry->ptr)
        {
            *access = entry->access & ~RESERVED_ALL;
            ret = TRUE;
        }
    }
    SYSTEM_UNLOCK();
    if (!ret) SetLastError( ERROR_INVALID_HANDLE );
    return ret;
}


/*********************************************************************
 *           HANDLE_Close
 */
static BOOL32 HANDLE_Close( PDB32 *pdb, HANDLE32 handle )
{
    BOOL32 ret = FALSE;
    K32OBJ *ptr;

    if (HANDLE_IS_GLOBAL( handle ))
    {
        handle = HANDLE_GLOBAL_TO_LOCAL( handle );
        pdb = PROCESS_Initial();
    }
    SYSTEM_LOCK();
    if ((handle > 0) && (handle < pdb->handle_table->count))
    {
        HANDLE_ENTRY *entry = &pdb->handle_table->entries[handle];
        if ((ptr = entry->ptr))
        {
            if (!(entry->access & RESERVED_CLOSE_PROTECT))
            {
                entry->access = 0;
                entry->ptr    = NULL;
                if (entry->server != -1)
                    CLIENT_CloseHandle( entry->server );
                K32OBJ_DecCount( ptr );
                ret = TRUE;
            }
            /* FIXME: else SetLastError */
        }
    }
    SYSTEM_UNLOCK();
    if (!ret) SetLastError( ERROR_INVALID_HANDLE );
    return ret;
}


/*********************************************************************
 *           HANDLE_CloseAll
 *
 * Close all handles pointing to a given object (or all handles of the
 * process if the object is NULL)
 */
void HANDLE_CloseAll( PDB32 *pdb, K32OBJ *obj )
{
    HANDLE_ENTRY *entry;
    K32OBJ *ptr;
    HANDLE32 handle;

    SYSTEM_LOCK();
    entry = pdb->handle_table->entries;
    for (handle = 0; handle < pdb->handle_table->count; handle++, entry++)
    {
        if (!(ptr = entry->ptr)) continue;  /* empty slot */
        if (obj && (ptr != obj)) continue;  /* not the right object */
        entry->access = 0;
        entry->ptr    = NULL;
        if (entry->server != -1) CLIENT_CloseHandle( entry->server );
        K32OBJ_DecCount( ptr );
    }
    SYSTEM_UNLOCK();
}


/*********************************************************************
 *           CloseHandle   (KERNEL32.23)
 */
BOOL32 WINAPI CloseHandle( HANDLE32 handle )
{
    return HANDLE_Close( PROCESS_Current(), handle );
}


/*********************************************************************
 *           GetHandleInformation   (KERNEL32.336)
 */
BOOL32 WINAPI GetHandleInformation( HANDLE32 handle, LPDWORD flags )
{
    BOOL32 ret = FALSE;
    PDB32 *pdb = PROCESS_Current();

    SYSTEM_LOCK();
    if ((handle > 0) && (handle < pdb->handle_table->count))
    {
        HANDLE_ENTRY *entry = &pdb->handle_table->entries[handle];
        if (entry->ptr)
        {
            if (flags)
                *flags = (entry->access & RESERVED_ALL) >> RESERVED_SHIFT;
            ret = TRUE;
        }
    }
    SYSTEM_UNLOCK();
    if (!ret) SetLastError( ERROR_INVALID_HANDLE );
    return ret;
}


/*********************************************************************
 *           SetHandleInformation   (KERNEL32.653)
 */
BOOL32 WINAPI SetHandleInformation( HANDLE32 handle, DWORD mask, DWORD flags )
{
    BOOL32 ret = FALSE;
    PDB32 *pdb = PROCESS_Current();

    mask  = (mask << RESERVED_SHIFT) & RESERVED_ALL;
    flags = (flags << RESERVED_SHIFT) & RESERVED_ALL;
    SYSTEM_LOCK();
    if ((handle > 0) && (handle < pdb->handle_table->count))
    {
        HANDLE_ENTRY *entry = &pdb->handle_table->entries[handle];
        if (entry->ptr)
        {
            entry->access = (entry->access & ~mask) | flags;
            ret = TRUE;
        }
    }
    SYSTEM_UNLOCK();
    if (!ret) SetLastError( ERROR_INVALID_HANDLE );
    return ret;
}


/*********************************************************************
 *           DuplicateHandle   (KERNEL32.192)
 */
BOOL32 WINAPI DuplicateHandle( HANDLE32 source_process, HANDLE32 source,
                               HANDLE32 dest_process, HANDLE32 *dest,
                               DWORD access, BOOL32 inherit, DWORD options )
{
    PDB32 *src_pdb = NULL, *dst_pdb = NULL;
    K32OBJ *obj = NULL;
    BOOL32 ret = FALSE;
    HANDLE32 handle;
    int src_process, src_handle, dst_process, dst_handle;

    SYSTEM_LOCK();

    if (!(src_pdb = PROCESS_GetPtr( source_process, PROCESS_DUP_HANDLE, &src_process )))
        goto done;
    if (!(obj = HANDLE_GetObjPtr( src_pdb, source, K32OBJ_UNKNOWN, 0, &src_handle )))
        goto done;

    /* Now that we are sure the source is valid, handle the options */

    if (options & DUPLICATE_SAME_ACCESS)
        HANDLE_GetAccess( src_pdb, source, &access );
    if (options & DUPLICATE_CLOSE_SOURCE)
        HANDLE_Close( src_pdb, source );

    /* And duplicate the handle in the dest process */

    if (!(dst_pdb = PROCESS_GetPtr( dest_process, PROCESS_DUP_HANDLE, &dst_process )))
        goto done;

    if ((src_process != -1) && (src_handle != -1) && (dst_process != -1))
        dst_handle = CLIENT_DuplicateHandle( src_process, src_handle, dst_process, -1,
                                             access, inherit, options );
    else
        dst_handle = -1;

    if ((handle = HANDLE_Alloc( dst_pdb, obj, access, inherit,
                                dst_handle )) != INVALID_HANDLE_VALUE32)
    {
        if (dest) *dest = handle;
        ret = TRUE;
    }

done:
    if (dst_pdb) K32OBJ_DecCount( &dst_pdb->header );
    if (obj) K32OBJ_DecCount( obj );
    if (src_pdb) K32OBJ_DecCount( &src_pdb->header );
    SYSTEM_UNLOCK();
    return ret;
}


/***********************************************************************
 *           ConvertToGlobalHandle    		(KERNEL32)
 */
HANDLE32 WINAPI ConvertToGlobalHandle(HANDLE32 hSrc)
{
    int src_handle, dst_handle;
    HANDLE32 handle;
    K32OBJ *obj = NULL;
    DWORD access;

    if (HANDLE_IS_GLOBAL(hSrc))
        return hSrc;

    if (!(obj = HANDLE_GetObjPtr( PROCESS_Current(), hSrc, K32OBJ_UNKNOWN, 0, &src_handle )))
        return 0;

    HANDLE_GetAccess( PROCESS_Current(), hSrc, &access );

    if (src_handle != -1)
        dst_handle = CLIENT_DuplicateHandle( GetCurrentProcess(), src_handle, -1, -1, 0, FALSE,
                                             DUP_HANDLE_MAKE_GLOBAL | DUP_HANDLE_SAME_ACCESS );
    else
        dst_handle = -1;

    if ((handle = HANDLE_Alloc( PROCESS_Initial(), obj, access, FALSE,
                                dst_handle )) != INVALID_HANDLE_VALUE32)
        handle = HANDLE_LOCAL_TO_GLOBAL(handle);
    else
        handle = 0;

    CloseHandle( hSrc );
    return handle;
}