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

/*++



Module Name:

    handleapi.cpp

Abstract:

    Implementation of the handle management APIs



--*/

#include "pal/handleapi.hpp"
#include "pal/handlemgr.hpp"
#include "pal/thread.hpp"
#include "pal/procobj.hpp"
#include "pal/dbgmsg.h"
#include "pal/process.h"

using namespace CorUnix;

SET_DEFAULT_DEBUG_CHANNEL(HANDLE);

CAllowedObjectTypes aotDuplicateHandle PAL_GLOBAL(TRUE);

PAL_ERROR
CloseSpecialHandle(
    HANDLE hObject
    );

/*++
Function:
  DuplicateHandle

See MSDN doc.

PAL-specific behavior :
    -Source and Target process needs to be the current process.
    -lpTargetHandle must be non-NULL
    -dwDesiredAccess is ignored
    -bInheritHandle must be FALSE
    -dwOptions must be a combo of DUPLICATE_SAME_ACCESS and
               DUPLICATE_CLOSE_SOURCE

--*/
BOOL
PALAPI
DuplicateHandle(
        IN HANDLE hSourceProcessHandle,
        IN HANDLE hSourceHandle,
        IN HANDLE hTargetProcessHandle,
        OUT LPHANDLE lpTargetHandle,
        IN DWORD dwDesiredAccess,
        IN BOOL bInheritHandle,
        IN DWORD dwOptions)
{
    PAL_ERROR palError;
    CPalThread *pThread;
    
    PERF_ENTRY(DuplicateHandle);
    ENTRY("DuplicateHandle( hSrcProcHandle=%p, hSrcHandle=%p, "
          "hTargetProcHandle=%p, lpTargetHandle=%p, dwAccess=%#x, "
          "bInheritHandle=%d, dwOptions=%#x) \n", hSourceProcessHandle,
          hSourceHandle, hTargetProcessHandle, lpTargetHandle,
          dwDesiredAccess, bInheritHandle, dwOptions);

    pThread = InternalGetCurrentThread();

    palError = InternalDuplicateHandle(
        pThread,
        hSourceProcessHandle,
        hSourceHandle,
        hTargetProcessHandle,
        lpTargetHandle,
        dwDesiredAccess,
        bInheritHandle,
        dwOptions
        );

    if (NO_ERROR != palError)
    {
        pThread->SetLastError(palError);
    }

    LOGEXIT("DuplicateHandle returns BOOL %d\n", (NO_ERROR == palError));
    PERF_EXIT(DuplicateHandle);
    return (NO_ERROR == palError);
}

PAL_ERROR
CorUnix::InternalDuplicateHandle(
    CPalThread *pThread,
    HANDLE hSourceProcess,
    HANDLE hSource,
    HANDLE hTargetProcess,
    LPHANDLE phDuplicate,
    DWORD dwDesiredAccess,
    BOOL bInheritHandle,
    DWORD dwOptions
    )
{
    PAL_ERROR palError = NO_ERROR;
    IPalObject *pobjSource = NULL;
    
    DWORD source_process_id;
    DWORD target_process_id;
    DWORD cur_process_id;

    cur_process_id = GetCurrentProcessId();
    source_process_id = PROCGetProcessIDFromHandle(hSourceProcess);
    target_process_id = PROCGetProcessIDFromHandle(hTargetProcess);

    /* Check validity of process handles */
    if (0 == source_process_id || 0 == target_process_id)
    {
        ASSERT("Can't duplicate handle: invalid source or destination process");
        palError = ERROR_INVALID_PARAMETER;
        goto InternalDuplicateHandleExit;
    }

    /* At least source or target process should be the current process. */
    if (source_process_id != cur_process_id
        && target_process_id != cur_process_id)
    {
        ASSERT("Can't duplicate handle : neither source or destination"
               "processes are from current process");
        palError = ERROR_INVALID_PARAMETER;
        goto InternalDuplicateHandleExit;
    }

    if (FALSE != bInheritHandle)
    {
        ASSERT("Can't duplicate handle : bInheritHandle is not FALSE.\n");
        palError = ERROR_INVALID_PARAMETER;
        goto InternalDuplicateHandleExit;
    }

    if (dwOptions & ~(DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE))
    {
        ASSERT(
            "Can't duplicate handle : dwOptions is %#x which is not "
            "a subset of (DUPLICATE_SAME_ACCESS|DUPLICATE_CLOSE_SOURCE) "
            "(%#x).\n",
            dwOptions,
            DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE);
        palError = ERROR_INVALID_PARAMETER;
        goto InternalDuplicateHandleExit;
    }
    
    if (0 == (dwOptions & DUPLICATE_SAME_ACCESS))
    {
        ASSERT(
            "Can't duplicate handle : dwOptions is %#x which does not "
            "include DUPLICATE_SAME_ACCESS (%#x).\n",
            dwOptions,
            DUPLICATE_SAME_ACCESS);
        palError = ERROR_INVALID_PARAMETER;
        goto InternalDuplicateHandleExit;
    }

    if (NULL == phDuplicate)
    {
        ASSERT("Can't duplicate handle : lpTargetHandle is NULL.\n");
        goto InternalDuplicateHandleExit;
    }

    /* Since handles can be remoted to others processes using PAL_LocalHsndleToRemote
       and PAL_RemoteHandleToLocal, DuplicateHandle needs some special handling
       when this scenario occurs.

       if hSourceProcessHandle is from another process OR
       hTargetProcessHandle is from another process but both aren't
       ( handled above ) return hSourceHandle.
    */
    if (source_process_id != cur_process_id
        || target_process_id != cur_process_id)
    {
        *phDuplicate = hSource;
        palError = NO_ERROR;
        goto InternalDuplicateHandleExit;
    }

    //
    // Obtain the source IPalObject
    //

    if (!HandleIsSpecial(hSource))
    {
        palError = g_pObjectManager->ReferenceObjectByHandle(
            pThread,
            hSource,
            &aotDuplicateHandle,
            dwDesiredAccess,
            &pobjSource
            );

        if (NO_ERROR != palError)
        {
            ERROR("Unable to get object for source handle %p (%i)\n", hSource, palError);
            goto InternalDuplicateHandleExit;
        }            
    }
    else if (hPseudoCurrentProcess == hSource)
    {
        TRACE("Duplicating process pseudo handle(%p)\n", hSource);

        pobjSource = g_pobjProcess;
        pobjSource->AddReference();
    }
    else if (hPseudoCurrentThread == hSource)
    {
        TRACE("Duplicating thread pseudo handle(%p)\n", hSource);

        pobjSource = pThread->GetThreadObject();
        pobjSource->AddReference();
    }
    else
    {
        ASSERT("Duplication not supported for this special handle (%p)\n", hSource);
        palError = ERROR_INVALID_HANDLE;
        goto InternalDuplicateHandleExit;
    }

    palError = g_pObjectManager->ObtainHandleForObject(
        pThread,
        pobjSource,
        dwDesiredAccess,
        bInheritHandle,
        NULL,
        phDuplicate
        );

InternalDuplicateHandleExit:

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

    if (dwOptions & DUPLICATE_CLOSE_SOURCE)
    {
        //
        // Since DUPLICATE_CLOSE_SOURCE was specified the source handle
        // MUST be closed, even if an error occurred during the duplication
        // process
        //
        
        TRACE("DuplicateHandle closing source handle %p\n", hSource);
        InternalCloseHandle(pThread, hSource);
    }

    return palError;
}

/*++
Function:
  CloseHandle

See MSDN doc.

Note : according to MSDN, FALSE is returned in case of error. But also
according to MSDN, closing an invalid handle raises an exception when running a
debugger [or, alternately, if a special registry key is set]. This behavior is
not required in the PAL, so we'll always return FALSE.
--*/
BOOL
PALAPI
CloseHandle(
        IN OUT HANDLE hObject)
{
    CPalThread *pThread;
    PAL_ERROR palError;

    PERF_ENTRY(CloseHandle);
    ENTRY("CloseHandle (hObject=%p) \n", hObject);

    pThread = InternalGetCurrentThread();

    palError = InternalCloseHandle(
        pThread,
        hObject
        );

    if (NO_ERROR != palError)
    {
        pThread->SetLastError(palError);
    }

    LOGEXIT("CloseHandle returns BOOL %d\n", (NO_ERROR == palError));
    PERF_EXIT(CloseHandle);
    return (NO_ERROR == palError);
}

PAL_ERROR
CorUnix::InternalCloseHandle(
    CPalThread * pThread,
    HANDLE hObject
    )
{
    PAL_ERROR palError = NO_ERROR;

    if (!HandleIsSpecial(hObject))
    {
        palError = g_pObjectManager->RevokeHandle(
            pThread,
            hObject
            );
    }
    else
    {
        palError = CloseSpecialHandle(hObject);
    }

    return palError;
}

PAL_ERROR
CloseSpecialHandle(
    HANDLE hObject
    )
{
    if ((hObject == hPseudoCurrentThread) ||
        (hObject == hPseudoCurrentProcess))
    {
        return NO_ERROR;
    }

    return ERROR_INVALID_HANDLE;
}

