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

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

/*++



Module Name:

    semaphore.cpp

Abstract:

    Implementation of the sempahore synchroniztion object as described in
    the WIN32 API

Revision History:



--*/

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

using namespace CorUnix;

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

CObjectType CorUnix::otSemaphore(
                otiSemaphore,
                NULL,   // No cleanup routine
                NULL,   // No initialization routine
                sizeof(SemaphoreImmutableData),
                // NULL,   // No immutable data copy routine
                // NULL,   // No immutable data cleanup routine
                0,      // No process local data
                // NULL,   // No process local data cleanup routine
                0,      // No shared data
                0,      // Should be SEMAPHORE_ALL_ACCESS; currently ignored (no Win32 security)
                CObjectType::SecuritySupported,
                CObjectType::SecurityInfoNotPersisted,
                CObjectType::UnnamedObject,
                CObjectType::LocalDuplicationOnly,
                CObjectType::WaitableObject,
                CObjectType::ObjectCanBeUnsignaled,
                CObjectType::ThreadReleaseAltersSignalCount,
                CObjectType::NoOwner
                );

CAllowedObjectTypes aotSempahore(otiSemaphore);

/*++
Function:
CreateSemaphoreExA

Note:
lpSemaphoreAttributes currently ignored:
-- Win32 object security not supported
-- handles to semaphore objects are not inheritable

Parameters:
See MSDN doc.
--*/

HANDLE
PALAPI
CreateSemaphoreExA(
        IN LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
        IN LONG lInitialCount,
        IN LONG lMaximumCount,
        IN LPCSTR lpName,
        IN /*_Reserved_*/  DWORD dwFlags,
        IN DWORD dwDesiredAccess)
{
    // dwFlags is reserved and unused, and dwDesiredAccess is currently
    // only ever used as SEMAPHORE_ALL_ACCESS.  The other parameters
    // all map to CreateSemaphoreA.
    _ASSERTE(SEMAPHORE_ALL_ACCESS == dwDesiredAccess);

    return CreateSemaphoreA(
        lpSemaphoreAttributes,
        lInitialCount,
        lMaximumCount,
        lpName);
}

/*++
Function:
  CreateSemaphoreA

Note:
  lpSemaphoreAttributes currently ignored:
  -- Win32 object security not supported
  -- handles to semaphore objects are not inheritable

Parameters:
  See MSDN doc.
--*/

HANDLE
PALAPI
CreateSemaphoreA(
         IN LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
         IN LONG lInitialCount,
         IN LONG lMaximumCount,
         IN LPCSTR lpName)
{
    HANDLE hSemaphore = NULL;
    CPalThread *pthr = NULL;
    PAL_ERROR palError;

    PERF_ENTRY(CreateSemaphoreA);
    ENTRY("CreateSemaphoreA(lpSemaphoreAttributes=%p, lInitialCount=%d, "
          "lMaximumCount=%d, lpName=%p (%s))\n",
          lpSemaphoreAttributes, lInitialCount, lMaximumCount, 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 = InternalCreateSemaphore(
            pthr,
            lpSemaphoreAttributes,
            lInitialCount,
            lMaximumCount,
            NULL,
            &hSemaphore
            );
    }

    //
    // 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("CreateSemaphoreA returns HANDLE %p\n", hSemaphore);
    PERF_EXIT(CreateSemaphoreA);
    return hSemaphore;
}

/*++
Function:
CreateSemaphoreExW

Note:
lpSemaphoreAttributes currentely ignored:
-- Win32 object security not supported
-- handles to semaphore objects are not inheritable

Parameters:
See MSDN doc.
--*/

HANDLE
PALAPI
CreateSemaphoreExW(
        IN LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
        IN LONG lInitialCount,
        IN LONG lMaximumCount,
        IN LPCWSTR lpName,
        IN /*_Reserved_*/  DWORD dwFlags,
        IN DWORD dwDesiredAccess)
{
    // dwFlags is reserved and unused

    return CreateSemaphoreW(
        lpSemaphoreAttributes,
        lInitialCount,
        lMaximumCount,
        lpName);
}

/*++
Function:
  CreateSemaphoreW

Note:
  lpSemaphoreAttributes currentely ignored:
  -- Win32 object security not supported
  -- handles to semaphore objects are not inheritable

Parameters:
  See MSDN doc.
--*/

HANDLE
PALAPI
CreateSemaphoreW(
         IN LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
         IN LONG lInitialCount,
         IN LONG lMaximumCount,
         IN LPCWSTR lpName)
{
    HANDLE hSemaphore = NULL;
    PAL_ERROR palError;
    CPalThread *pthr = NULL;

    PERF_ENTRY(CreateSemaphoreW);
    ENTRY("CreateSemaphoreW(lpSemaphoreAttributes=%p, lInitialCount=%d, "
          "lMaximumCount=%d, lpName=%p (%S))\n",
          lpSemaphoreAttributes, lInitialCount, lMaximumCount,
          lpName, lpName?lpName:W16_NULLSTRING);

    pthr = InternalGetCurrentThread();

    palError = InternalCreateSemaphore(
        pthr,
        lpSemaphoreAttributes,
        lInitialCount,
        lMaximumCount,
        lpName,
        &hSemaphore
        );

    //
    // 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("CreateSemaphoreW returns HANDLE %p\n", hSemaphore);
    PERF_EXIT(CreateSemaphoreW);
    return hSemaphore;
}

/*++
Function:
  InternalCreateSemaphore

Note:
  lpSemaphoreAttributes currentely ignored:
  -- Win32 object security not supported
  -- handles to semaphore objects are not inheritable

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

  See MSDN docs on CreateSemaphore for all other parameters.
--*/

PAL_ERROR
CorUnix::InternalCreateSemaphore(
    CPalThread *pthr,
    LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
    LONG lInitialCount,
    LONG lMaximumCount,
    LPCWSTR lpName,
    HANDLE *phSemaphore
    )
{
    CObjectAttributes oa(lpName, lpSemaphoreAttributes);
    PAL_ERROR palError = NO_ERROR;
    IPalObject *pobjSemaphore = NULL;
    IPalObject *pobjRegisteredSemaphore = NULL;
    SemaphoreImmutableData *pSemaphoreData;

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

    ENTRY("InternalCreateSemaphore(pthr=%p, lpSemaphoreAttributes=%p, "
        "lInitialCount=%d, lMaximumCount=%d, lpName=%p, phSemaphore=%p)\n",
        pthr,
        lpSemaphoreAttributes,
        lInitialCount,
        lMaximumCount,
        lpName,
        phSemaphore
        );

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

    if (lMaximumCount <= 0)
    {
        ERROR("lMaximumCount is invalid (%d)\n", lMaximumCount);
        palError = ERROR_INVALID_PARAMETER;
        goto InternalCreateSemaphoreExit;
    }

    if ((lInitialCount < 0) || (lInitialCount > lMaximumCount))
    {
        ERROR("lInitialCount is invalid (%d)\n", lInitialCount);
        palError = ERROR_INVALID_PARAMETER;
        goto InternalCreateSemaphoreExit;
    }

    palError = g_pObjectManager->AllocateObject(
        pthr,
        &otSemaphore,
        &oa,
        &pobjSemaphore
        );

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

    palError = pobjSemaphore->GetImmutableData(reinterpret_cast<void**>(&pSemaphoreData));

    if (NO_ERROR != palError)
    {
        ASSERT("Error %d obtaining object data\n", palError);
        goto InternalCreateSemaphoreExit;
    }

    pSemaphoreData->lMaximumCount = lMaximumCount;

    if (0 != lInitialCount)
    {
        ISynchStateController *pssc;

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

        if (NO_ERROR == palError)
        {
            palError = pssc->SetSignalCount(lInitialCount);
            pssc->ReleaseController();
        }

        if (NO_ERROR != palError)
        {
            ASSERT("Unable to set new semaphore state (%d)\n", palError);
            goto InternalCreateSemaphoreExit;
        }
    }

    palError = g_pObjectManager->RegisterObject(
        pthr,
        pobjSemaphore,
        &aotSempahore,
        0, // Should be SEMAPHORE_ALL_ACCESS; currently ignored (no Win32 security)
        phSemaphore,
        &pobjRegisteredSemaphore
        );

    //
    // pobjSemaphore 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.
    //

    pobjSemaphore = NULL;

InternalCreateSemaphoreExit:

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

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

    LOGEXIT("InternalCreateSemaphore returns %d\n", palError);

    return palError;
}


/*++
Function:
  ReleaseSemaphore

Parameters:
  See MSDN doc.
--*/

BOOL
PALAPI
ReleaseSemaphore(
         IN HANDLE hSemaphore,
         IN LONG lReleaseCount,
         OUT LPLONG lpPreviousCount)
{
    PAL_ERROR palError = NO_ERROR;
    CPalThread *pthr = NULL;

    PERF_ENTRY(ReleaseSemaphore);
    ENTRY("ReleaseSemaphore(hSemaphore=%p, lReleaseCount=%d, "
          "lpPreviousCount=%p)\n",
          hSemaphore, lReleaseCount, lpPreviousCount);

    pthr = InternalGetCurrentThread();

    palError = InternalReleaseSemaphore(
        pthr,
        hSemaphore,
        lReleaseCount,
        lpPreviousCount
        );

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

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

/*++
Function:
  InternalReleaseSemaphore

Parameters:
  pthr -- thread data for calling thread

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

PAL_ERROR
CorUnix::InternalReleaseSemaphore(
    CPalThread *pthr,
    HANDLE hSemaphore,
    LONG lReleaseCount,
    LPLONG lpPreviousCount
    )
{
    PAL_ERROR palError = NO_ERROR;
    IPalObject *pobjSemaphore = NULL;
    ISynchStateController *pssc = NULL;
    SemaphoreImmutableData *pSemaphoreData;
    LONG lOldCount;

    _ASSERTE(NULL != pthr);

    ENTRY("InternalReleaseSempahore(pthr=%p, hSemaphore=%p, lReleaseCount=%d, "
        "lpPreviousCount=%p)\n",
        pthr,
        hSemaphore,
        lReleaseCount,
        lpPreviousCount
        );

    if (0 >= lReleaseCount)
    {
        palError = ERROR_INVALID_PARAMETER;
        goto InternalReleaseSemaphoreExit;
    }

    palError = g_pObjectManager->ReferenceObjectByHandle(
        pthr,
        hSemaphore,
        &aotSempahore,
        0, // Should be SEMAPHORE_MODIFY_STATE; currently ignored (no Win32 security)
        &pobjSemaphore
        );

    if (NO_ERROR != palError)
    {
        ERROR("Unable to obtain object for handle %p (error %d)!\n", hSemaphore, palError);
        goto InternalReleaseSemaphoreExit;
    }

    palError = pobjSemaphore->GetImmutableData(reinterpret_cast<void**>(&pSemaphoreData));

    if (NO_ERROR != palError)
    {
        ASSERT("Error %d obtaining object data\n", palError);
        goto InternalReleaseSemaphoreExit;
    }

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

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

    palError = pssc->GetSignalCount(&lOldCount);

    if (NO_ERROR != palError)
    {
        ASSERT("Error %d obtaining current signal count\n", palError);
        goto InternalReleaseSemaphoreExit;
    }

    _ASSERTE(lOldCount <= pSemaphoreData->lMaximumCount);
    if (lReleaseCount > pSemaphoreData->lMaximumCount - lOldCount)
    {
        palError = ERROR_INVALID_PARAMETER;
        goto InternalReleaseSemaphoreExit;
    }

    palError = pssc->IncrementSignalCount(lReleaseCount);

    if (NO_ERROR != palError)
    {
        ASSERT("Error %d incrementing signal count\n", palError);
        goto InternalReleaseSemaphoreExit;
    }

    if (NULL != lpPreviousCount)
    {
        *lpPreviousCount = lOldCount;
    }

InternalReleaseSemaphoreExit:

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

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

    LOGEXIT("InternalReleaseSemaphore returns %d\n", palError);

    return palError;
}

// TODO: Implementation of OpenSemaphoreA() doesn't exist, do we need it? More generally, do we need the A versions at all?

/*++
Function:
  OpenSemaphoreW

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

Parameters:
  See MSDN doc.
--*/

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

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

    pthr = InternalGetCurrentThread();

    /* validate parameters */
    if (lpName == nullptr)
    {
        ERROR("lpName 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("OpenSemaphoreW returns HANDLE %p\n", hSemaphore);
    PERF_EXIT(OpenSemaphoreW);

    return hSemaphore;
}