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

/*++



Module Name:

    mutex.ccpp

Abstract:

    Implementation of mutex synchroniztion object as described in 
    the WIN32 API

Revision History:



--*/

#include "pal/mutex.hpp"
#include "pal/thread.hpp"
#include "pal/dbgmsg.h"

using namespace CorUnix;

/* ------------------- Definitions ------------------------------*/
SET_DEFAULT_DEBUG_CHANNEL(SYNC);

enum
{
    c_cchMaxMutex = MAX_LONGPATH + 1
};

CObjectType CorUnix::otMutex PAL_GLOBAL (
                otiMutex,
                NULL,   // No cleanup routine
                NULL,   // No initialization routine
                0,      // No immutable data
                0,      // No process local data
                0,      // No shared data
                0,      // Should be MUTEX_ALL_ACCESS; currently ignored (no Win32 security)
                CObjectType::SecuritySupported,
                CObjectType::SecurityInfoNotPersisted,
                CObjectType::ObjectCanHaveName,
                CObjectType::CrossProcessDuplicationAllowed,
                CObjectType::WaitableObject,
                CObjectType::ObjectCanBeUnsignaled,
                CObjectType::ThreadReleaseAltersSignalCount,
                CObjectType::OwnershipTracked
                );

CAllowedObjectTypes aotMutex PAL_GLOBAL (otiMutex);

/*++
Function:
  CreateMutexA

Note:
  lpMutexAttributes currentely ignored:
  -- Win32 object security not supported
  -- handles to mutex objects are not inheritable

Parameters:
  See MSDN doc.
--*/

HANDLE
PALAPI
CreateMutexA(
    IN LPSECURITY_ATTRIBUTES lpMutexAttributes,
    IN BOOL bInitialOwner,
    IN LPCSTR lpName)
{
    HANDLE hMutex = NULL;
    CPalThread *pthr = NULL;
    PAL_ERROR palError;
    
    PERF_ENTRY(CreateMutexA);
    ENTRY("CreateMutexA(lpMutexAttr=%p, bInitialOwner=%d, lpName=%p (%s)\n",
          lpMutexAttributes, bInitialOwner, lpName, lpName?lpName:"NULL");

    pthr = InternalGetCurrentThread();
    
    if (lpName != nullptr)
    {
        ASSERT("lpName: Cross-process named objects are not supported in PAL");
        palError = ERROR_NOT_SUPPORTED;
    }
    else
    {
        palError = InternalCreateMutex(
            pthr,
            lpMutexAttributes,
            bInitialOwner,
            NULL,
            &hMutex
            );
    }

    //
    // We always need to set last error, even on success:
    // we need to protect ourselves from the situation
    // where last error is set to ERROR_ALREADY_EXISTS on
    // entry to the function
    //

    pthr->SetLastError(palError);
    
    LOGEXIT("CreateMutexA returns HANDLE %p\n", hMutex);
    PERF_EXIT(CreateMutexA);
    return hMutex;
}


/*++
Function:
  CreateMutexW

Note:
  lpMutexAttributes currentely ignored:
  -- Win32 object security not supported
  -- handles to mutex objects are not inheritable

Parameters:
  See MSDN doc.
--*/

HANDLE
PALAPI
CreateMutexW(
    IN LPSECURITY_ATTRIBUTES lpMutexAttributes,
    IN BOOL bInitialOwner,
    IN LPCWSTR lpName)
{
    HANDLE hMutex = NULL;
    PAL_ERROR palError;
    CPalThread *pthr = NULL;

    PERF_ENTRY(CreateMutexW);
    ENTRY("CreateMutexW(lpMutexAttr=%p, bInitialOwner=%d, lpName=%p (%S)\n",
          lpMutexAttributes, bInitialOwner, lpName, lpName?lpName:W16_NULLSTRING);

    pthr = InternalGetCurrentThread();

    palError = InternalCreateMutex(
        pthr,
        lpMutexAttributes,
        bInitialOwner,
        lpName,
        &hMutex
        );

    //
    // We always need to set last error, even on success:
    // we need to protect ourselves from the situation
    // where last error is set to ERROR_ALREADY_EXISTS on
    // entry to the function
    //

    pthr->SetLastError(palError);

    LOGEXIT("CreateMutexW returns HANDLE %p\n", hMutex);
    PERF_EXIT(CreateMutexW);
    return hMutex;
}

/*++
Function:
  InternalCreateMutex

Note:
  lpMutexAttributes currentely ignored:
  -- Win32 object security not supported
  -- handles to mutex objects are not inheritable

Parameters:
  pthr -- thread data for calling thread
  phEvent -- on success, receives the allocated mutex handle

  See MSDN docs on CreateMutex for all other parameters
--*/

PAL_ERROR
CorUnix::InternalCreateMutex(
    CPalThread *pthr,
    LPSECURITY_ATTRIBUTES lpMutexAttributes,
    BOOL bInitialOwner,
    LPCWSTR lpName,
    HANDLE *phMutex
    )
{
    CObjectAttributes oa(lpName, lpMutexAttributes);
    PAL_ERROR palError = NO_ERROR;
    IPalObject *pobjMutex = NULL;
    IPalObject *pobjRegisteredMutex = NULL;
    ISynchStateController *pssc = NULL;

    _ASSERTE(NULL != pthr);
    _ASSERTE(NULL != phMutex);

    ENTRY("InternalCreateMutex(pthr=%p, lpMutexAttributes=%p, bInitialOwner=%d"
        ", lpName=%p, phMutex=%p)\n",
        pthr,
        lpMutexAttributes,
        bInitialOwner,
        lpName,
        phMutex
        );

    if (lpName != nullptr)
    {
        ASSERT("lpName: Cross-process named objects are not supported in PAL");
        palError = ERROR_NOT_SUPPORTED;
        goto InternalCreateMutexExit;
    }

    palError = g_pObjectManager->AllocateObject(
        pthr,
        &otMutex,
        &oa,
        &pobjMutex
        );

    if (NO_ERROR != palError)
    {
        goto InternalCreateMutexExit;
    }

    palError = pobjMutex->GetSynchStateController(
        pthr,
        &pssc
        );

    if (NO_ERROR != palError)
    {
        ASSERT("Unable to create state controller (%d)\n", palError);
        goto InternalCreateMutexExit;
    }

    if (bInitialOwner)
    {
        palError = pssc->SetOwner(pthr);
    }
    else
    {
        palError = pssc->SetSignalCount(1);
    }

    pssc->ReleaseController();

    if (NO_ERROR != palError)
    {
        ASSERT("Unable to set initial mutex state (%d)\n", palError);
        goto InternalCreateMutexExit;
    }

    palError = g_pObjectManager->RegisterObject(
        pthr,
        pobjMutex,
        &aotMutex, 
        0, // should be MUTEX_ALL_ACCESS -- currently ignored (no Win32 security)
        phMutex,
        &pobjRegisteredMutex
        );

    //
    // pobjMutex is invalidated by the call to RegisterObject, so NULL it
    // out here to ensure that we don't try to release a reference on
    // it down the line.
    //
    
    pobjMutex = NULL;

InternalCreateMutexExit:

    if (NULL != pobjMutex)
    {
        pobjMutex->ReleaseReference(pthr);
    }

    if (NULL != pobjRegisteredMutex)
    {
        pobjRegisteredMutex->ReleaseReference(pthr);
    }

    LOGEXIT("InternalCreateMutex returns %i\n", palError);

    return palError;
}

/*++
Function:
  ReleaseMutex

Parameters:
  See MSDN doc.
--*/

BOOL
PALAPI
ReleaseMutex( IN HANDLE hMutex )
{
    PAL_ERROR palError = NO_ERROR;
    CPalThread *pthr = NULL;
    
    PERF_ENTRY(ReleaseMutex);
    ENTRY("ReleaseMutex(hMutex=%p)\n", hMutex);

    pthr = InternalGetCurrentThread();
    
    palError = InternalReleaseMutex(pthr, hMutex);

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

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

/*++
Function:
  InternalReleaseMutex

Parameters:
  pthr -- thread data for calling thread

  See MSDN docs on ReleaseMutex for all other parameters
--*/

PAL_ERROR
CorUnix::InternalReleaseMutex(
    CPalThread *pthr,
    HANDLE hMutex
    )
{
    PAL_ERROR palError = NO_ERROR;
    IPalObject *pobjMutex = NULL;
    ISynchStateController *pssc = NULL;

    _ASSERTE(NULL != pthr);

    ENTRY("InternalReleaseMutex(pthr=%p, hMutex=%p)\n",
        pthr,
        hMutex
        );

    palError = g_pObjectManager->ReferenceObjectByHandle(
        pthr,
        hMutex,
        &aotMutex,
        0, // should be MUTEX_MODIFY_STATE -- current ignored (no Win32 security)
        &pobjMutex
        );

    if (NO_ERROR != palError)
    {
        ERROR("Unable to obtain object for handle %p (error %d)!\n", hMutex, palError);
        goto InternalReleaseMutexExit;
    }
    
    palError = pobjMutex->GetSynchStateController(
        pthr,
        &pssc
        );

    if (NO_ERROR != palError)
    {
        ASSERT("Error %d obtaining synch state controller\n", palError);
        goto InternalReleaseMutexExit;
    }

    palError = pssc->DecrementOwnershipCount();

    if (NO_ERROR != palError)
    {
        ERROR("Error %d decrementing mutex ownership count\n", palError);
        goto InternalReleaseMutexExit;
    }

InternalReleaseMutexExit:

    if (NULL != pssc)
    {
        pssc->ReleaseController();
    }

    if (NULL != pobjMutex)
    {
        pobjMutex->ReleaseReference(pthr);
    }

    LOGEXIT("InternalReleaseMutex returns %i\n", palError);

    return palError;
}

/*++
Function:
  OpenMutexA

Note:
  dwDesiredAccess is currently ignored (no Win32 object security support)
  bInheritHandle is currently ignored (handles to mutexes are not inheritable)

See MSDN doc.
--*/

HANDLE
PALAPI
OpenMutexA (
       IN DWORD dwDesiredAccess,
       IN BOOL bInheritHandle,
       IN LPCSTR lpName)
{
    HANDLE hMutex = NULL;
    CPalThread *pthr = NULL;
    PAL_ERROR palError;
    
    PERF_ENTRY(OpenMutexA);
    ENTRY("OpenMutexA(dwDesiredAccess=%#x, bInheritHandle=%d, lpName=%p (%s))\n", 
          dwDesiredAccess, bInheritHandle, lpName, lpName?lpName:"NULL");

    pthr = InternalGetCurrentThread();

    /* validate parameters */
    if (lpName == nullptr)
    {
        ERROR("name is NULL\n");
        palError = ERROR_INVALID_PARAMETER;
    }
    else
    {
        ASSERT("lpName: Cross-process named objects are not supported in PAL");
        palError = ERROR_NOT_SUPPORTED;
    }

    if (NO_ERROR != palError)
    {
        pthr->SetLastError(palError);
    }
        
    LOGEXIT("OpenMutexA returns HANDLE %p\n", hMutex);
    PERF_EXIT(OpenMutexA);
    return hMutex;
}

/*++
Function:
  OpenMutexW

Note:
  dwDesiredAccess is currently ignored (no Win32 object security support)
  bInheritHandle is currently ignored (handles to mutexes are not inheritable)

See MSDN doc.
--*/

HANDLE
PALAPI
OpenMutexW(
       IN DWORD dwDesiredAccess,
       IN BOOL bInheritHandle,
       IN LPCWSTR lpName)
{
    HANDLE hMutex = NULL;
    PAL_ERROR palError = NO_ERROR;
    CPalThread *pthr = NULL;

    PERF_ENTRY(OpenMutexW);
    ENTRY("OpenMutexW(dwDesiredAccess=%#x, bInheritHandle=%d, lpName=%p (%S))\n", 
          dwDesiredAccess, bInheritHandle, lpName, lpName?lpName:W16_NULLSTRING);

    pthr = InternalGetCurrentThread();

    /* validate parameters */
    if (lpName == nullptr)
    {
        ERROR("name is NULL\n");
        palError = ERROR_INVALID_PARAMETER;
    }
    else
    {
        ASSERT("lpName: Cross-process named objects are not supported in PAL");
        palError = ERROR_NOT_SUPPORTED;
    }

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

    LOGEXIT("OpenMutexW returns HANDLE %p\n", hMutex);
    PERF_EXIT(OpenMutexW);

    return hMutex;
}

/*++
Function:
  InternalOpenMutex

Note:
  dwDesiredAccess is currently ignored (no Win32 object security support)
  bInheritHandle is currently ignored (handles to mutexes are not inheritable)

Parameters:
  pthr -- thread data for calling thread
  phEvent -- on success, receives the allocated mutex handle
  
  See MSDN docs on OpenMutex for all other parameters.
--*/

PAL_ERROR
CorUnix::InternalOpenMutex(
    CPalThread *pthr,
    DWORD dwDesiredAccess,
    BOOL bInheritHandle,
    LPCWSTR lpName,
    HANDLE *phMutex
    )
{
    PAL_ERROR palError = NO_ERROR;
    IPalObject *pobjMutex = NULL;
    CPalString sObjectName(lpName);

    _ASSERTE(NULL != pthr);
    _ASSERTE(NULL != lpName);
    _ASSERTE(NULL != phMutex);

    ENTRY("InternalOpenMutex(pthr=%p, dwDesiredAcces=%d, bInheritHandle=%d, "
        "lpName=%p, phMutex=%p)\n",
        pthr,
        dwDesiredAccess,
        bInheritHandle,
        lpName,
        phMutex
        );

    palError = g_pObjectManager->LocateObject(
        pthr,
        &sObjectName,
        &aotMutex,
        &pobjMutex
        );

    if (NO_ERROR != palError)
    {
        goto InternalOpenMutexExit;
    }

    palError = g_pObjectManager->ObtainHandleForObject(
        pthr,
        pobjMutex,
        dwDesiredAccess,
        bInheritHandle,
        NULL,
        phMutex
        );

    if (NO_ERROR != palError)
    {
        goto InternalOpenMutexExit;
    }

InternalOpenMutexExit:

    if (NULL != pobjMutex)
    {
        pobjMutex->ReleaseReference(pthr);
    }

    LOGEXIT("InternalOpenMutex returns %d\n", palError);
    
    return palError;
}


/* Basic spinlock implementation */
void SPINLOCKAcquire (LONG * lock, unsigned int flags)
{
    size_t loop_seed = 1, loop_count = 0;

    if (flags & SYNCSPINLOCK_F_ASYMMETRIC)
    {
        loop_seed = ((size_t)pthread_self() % 10) + 1;
    }
    while (InterlockedCompareExchange(lock, 1, 0))
    {
        if (!(flags & SYNCSPINLOCK_F_ASYMMETRIC) || (++loop_count % loop_seed))
        {
#if PAL_IGNORE_NORMAL_THREAD_PRIORITY
            struct timespec tsSleepTime;
            tsSleepTime.tv_sec = 0;
            tsSleepTime.tv_nsec = 1;
            nanosleep(&tsSleepTime, NULL);
#else
            sched_yield();
#endif 
        }
    }
    
}

void SPINLOCKRelease (LONG * lock)
{
    *lock = 0;
}

DWORD SPINLOCKTryAcquire (LONG * lock)
{
    return InterlockedCompareExchange(lock, 1, 0);
    // only returns 0 or 1.
}
