// ==++==
// 
//   Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// ==--==
//
// <OWNER>[....]</OWNER>
/*=============================================================================
**
** Class: Mutex
**
**
** Purpose: synchronization primitive that can also be used for interprocess synchronization
**
**
=============================================================================*/
namespace System.Threading 
{  
    using System;
    using System.Threading;
    using System.Runtime.CompilerServices;
    using System.Security.Permissions;
    using System.IO;
    using Microsoft.Win32;
    using Microsoft.Win32.SafeHandles;
    using System.Runtime.InteropServices;
    using System.Runtime.ConstrainedExecution;
    using System.Runtime.Versioning;
    using System.Security.Principal;
    using System.Security;
    using System.Diagnostics.Contracts;
    
#if FEATURE_MACL
    using System.Security.AccessControl;
#endif

    [HostProtection(Synchronization=true, ExternalThreading=true)]
    [ComVisible(true)]
    public sealed class Mutex : WaitHandle
    {
        static bool dummyBool;

#if !FEATURE_MACL
        public class MutexSecurity {
        }
#endif       

        [System.Security.SecurityCritical]  // auto-generated_required
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        [ResourceExposure(ResourceScope.None)]
        [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
        public Mutex(bool initiallyOwned, String name, out bool createdNew)
            : this(initiallyOwned, name, out createdNew, (MutexSecurity)null)
        {
        }

        [System.Security.SecurityCritical]  // auto-generated_required
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        [ResourceExposure(ResourceScope.Machine)]
        [ResourceConsumption(ResourceScope.Machine)]
        public unsafe Mutex(bool initiallyOwned, String name, out bool createdNew, MutexSecurity mutexSecurity)
        {
            if(null != name && System.IO.Path.MAX_PATH < name.Length)
            {
                throw new ArgumentException(Environment.GetResourceString("Argument_WaitHandleNameTooLong",name));
            }            
            Contract.EndContractBlock();
            Win32Native.SECURITY_ATTRIBUTES secAttrs = null;
#if FEATURE_MACL
            // For ACL's, get the security descriptor from the MutexSecurity.
            if (mutexSecurity != null) {

                secAttrs = new Win32Native.SECURITY_ATTRIBUTES();
                secAttrs.nLength = (int)Marshal.SizeOf(secAttrs);

                byte[] sd = mutexSecurity.GetSecurityDescriptorBinaryForm();
                byte* pSecDescriptor = stackalloc byte[sd.Length];
                Buffer.Memcpy(pSecDescriptor, 0, sd, 0, sd.Length);
                secAttrs.pSecurityDescriptor = pSecDescriptor;
            }
#endif

            CreateMutexWithGuaranteedCleanup(initiallyOwned, name, out createdNew, secAttrs);
        }

        [System.Security.SecurityCritical]  // auto-generated_required
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        [ResourceExposure(ResourceScope.Machine)]
        [ResourceConsumption(ResourceScope.Machine)]
        internal Mutex(bool initiallyOwned, String name, out bool createdNew, Win32Native.SECURITY_ATTRIBUTES secAttrs) 
        {
            if (null != name && Path.MAX_PATH < name.Length) 
            {
                throw new ArgumentException(Environment.GetResourceString("Argument_WaitHandleNameTooLong", name));
            }
            Contract.EndContractBlock();

            CreateMutexWithGuaranteedCleanup(initiallyOwned, name, out createdNew, secAttrs);
        }

#if FEATURE_LEGACYNETCF
        static string WinCEObjectNameQuirk(string name)
        {
            if (name == null)
                return null;

            // WinCE allowed backslashes in kernel object names, but WinNT does not allow them.
            // Replace all backslashes with a rare unicode character if we are in NetCF compat mode.
            // Mutex was the only named kernel object exposed to phone apps, so we do not have
            // to apply this quirk in other places.
            return name.Replace('\\', '\u2044');
        }
#endif

        [System.Security.SecurityCritical]  // auto-generated_required
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        [ResourceExposure(ResourceScope.Machine)]
        [ResourceConsumption(ResourceScope.Machine)]
        internal void CreateMutexWithGuaranteedCleanup(bool initiallyOwned, String name, out bool createdNew, Win32Native.SECURITY_ATTRIBUTES secAttrs)
        {
#if FEATURE_LEGACYNETCF
            if (CompatibilitySwitches.IsAppEarlierThanWindowsPhone8)
                name = WinCEObjectNameQuirk(name);
#endif

            RuntimeHelpers.CleanupCode cleanupCode = new RuntimeHelpers.CleanupCode(MutexCleanupCode);
            MutexCleanupInfo cleanupInfo = new MutexCleanupInfo(null, false);
            MutexTryCodeHelper tryCodeHelper = new MutexTryCodeHelper(initiallyOwned, cleanupInfo, name, secAttrs, this);
            RuntimeHelpers.TryCode tryCode = new RuntimeHelpers.TryCode(tryCodeHelper.MutexTryCode);
            RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(
                tryCode,
                cleanupCode,
                cleanupInfo);             
            createdNew = tryCodeHelper.m_newMutex;
        }

        internal class MutexTryCodeHelper 
        {
            bool m_initiallyOwned;
            MutexCleanupInfo m_cleanupInfo;
            internal bool m_newMutex;
            String m_name;
            [System.Security.SecurityCritical] // auto-generated
            Win32Native.SECURITY_ATTRIBUTES m_secAttrs;
            Mutex m_mutex;

            [System.Security.SecurityCritical]  // auto-generated
            [PrePrepareMethod]
            internal MutexTryCodeHelper(bool initiallyOwned,MutexCleanupInfo cleanupInfo, String name, Win32Native.SECURITY_ATTRIBUTES secAttrs, Mutex mutex)
            {
                m_initiallyOwned = initiallyOwned;
                m_cleanupInfo = cleanupInfo;
                m_name = name;
                m_secAttrs = secAttrs;
                m_mutex = mutex;
            }

            [System.Security.SecurityCritical]  // auto-generated
            [PrePrepareMethod]
            internal void MutexTryCode(object userData)
            {  
                SafeWaitHandle mutexHandle = null;
                // try block                
                RuntimeHelpers.PrepareConstrainedRegions();
                try 
                {
                }
                finally 
                {
                    if (m_initiallyOwned) 
                    {
                        m_cleanupInfo.inCriticalRegion = true;
#if !FEATURE_CORECLR
                        Thread.BeginThreadAffinity();
                        Thread.BeginCriticalRegion();
#endif //!FEATURE_CORECLR
                    }
                }

                int errorCode = 0;                    
                RuntimeHelpers.PrepareConstrainedRegions();
                try 
                {
                }
                finally 
                {
                    errorCode = CreateMutexHandle(m_initiallyOwned, m_name, m_secAttrs, out mutexHandle);
                }                    

                if (mutexHandle.IsInvalid) 
                {
                    mutexHandle.SetHandleAsInvalid();
                    if(null != m_name && 0 != m_name.Length && Win32Native.ERROR_INVALID_HANDLE == errorCode)
                        throw new WaitHandleCannotBeOpenedException(Environment.GetResourceString("Threading.WaitHandleCannotBeOpenedException_InvalidHandle", m_name));
                    __Error.WinIOError(errorCode, m_name);
                }
                m_newMutex = errorCode != Win32Native.ERROR_ALREADY_EXISTS;
                m_mutex.SetHandleInternal(mutexHandle);

                m_mutex.hasThreadAffinity = true;

            }
        }

        [System.Security.SecurityCritical]  // auto-generated
        [PrePrepareMethod]
        private void MutexCleanupCode(Object userData, bool exceptionThrown)
        {
            MutexCleanupInfo cleanupInfo = (MutexCleanupInfo) userData;
            
            // If hasThreadAffinity isn't true, we've thrown an exception in the above try, and we must free the mutex 
            // on this OS thread before ending our thread affninity.                
            if(!hasThreadAffinity) {
                if (cleanupInfo.mutexHandle != null && !cleanupInfo.mutexHandle.IsInvalid) {
                    if( cleanupInfo.inCriticalRegion) {
                        Win32Native.ReleaseMutex(cleanupInfo.mutexHandle);                    
                    }
                    cleanupInfo.mutexHandle.Dispose();                        
                    
                }
                    
                if( cleanupInfo.inCriticalRegion) {
#if !FEATURE_CORECLR
                    Thread.EndCriticalRegion();
                    Thread.EndThreadAffinity();
#endif
                }                    
            }
        }

        internal class MutexCleanupInfo
        {
            [System.Security.SecurityCritical] // auto-generated
            internal SafeWaitHandle mutexHandle;
            internal bool inCriticalRegion;
            [System.Security.SecurityCritical]  // auto-generated
            internal MutexCleanupInfo(SafeWaitHandle mutexHandle, bool inCriticalRegion)
            {
                this.mutexHandle = mutexHandle;
                this.inCriticalRegion = inCriticalRegion;
            }
        }

        // For the .NET Compact Framework this constructor was security safe critical.
        // For Windows Phone version 8 (Apollo), all apps will run as fully trusted,
        // meaning the CLR is not considered a trust boundary.  This API could be marked security critical.
        // However for Windows Phone version 7.1 applications, they will still be run
        // as partially trusted applications, with our security transparency model enforced.
        // So we have this peculiar #ifdef that should be enabled only for .NET CF backwards
        // compatibility.
#if FEATURE_LEGACYNETCF
        [System.Security.SecuritySafeCritical]  // auto-generated_required
#else
        [System.Security.SecurityCritical]  // auto-generated_required
#endif //FEATURE_LEGACYNETCF
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        [ResourceExposure(ResourceScope.Machine)]
        [ResourceConsumption(ResourceScope.Machine)]
        public Mutex(bool initiallyOwned, String name) : this(initiallyOwned, name, out dummyBool) {
        }

        [System.Security.SecuritySafeCritical]  // auto-generated
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        [ResourceExposure(ResourceScope.None)]
        [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
        public Mutex(bool initiallyOwned) : this(initiallyOwned, null, out dummyBool)
        {
        }

        [System.Security.SecuritySafeCritical]  // auto-generated
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        [ResourceExposure(ResourceScope.None)]
        [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
        public Mutex() : this(false, null, out dummyBool)
        {
        }
        
        [System.Security.SecurityCritical]  // auto-generated
        [ResourceExposure(ResourceScope.Machine)]
        [ResourceConsumption(ResourceScope.Machine)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        private Mutex(SafeWaitHandle handle)
        {
            SetHandleInternal(handle);
            hasThreadAffinity = true;
        }

        [System.Security.SecurityCritical]  // auto-generated_required
        [ResourceExposure(ResourceScope.Machine)]
        [ResourceConsumption(ResourceScope.Machine)]
        public static Mutex OpenExisting(string name)
        {
#if !FEATURE_MACL
            return OpenExisting(name, (MutexRights) 0);
#else // FEATURE_PAL
            return OpenExisting(name, MutexRights.Modify | MutexRights.Synchronize);
#endif // FEATURE_PAL
        }

#if !FEATURE_MACL
        public enum MutexRights
        {
        }
#endif

        [System.Security.SecurityCritical]  // auto-generated_required
        [ResourceExposure(ResourceScope.Machine)]
        [ResourceConsumption(ResourceScope.Machine)]
        public static Mutex OpenExisting(string name, MutexRights rights)
        {
            Mutex result;
            switch (OpenExistingWorker(name, rights, out result))
            {
                case OpenExistingResult.NameNotFound:
                    throw new WaitHandleCannotBeOpenedException();

                case OpenExistingResult.NameInvalid:
                    throw new WaitHandleCannotBeOpenedException(Environment.GetResourceString("Threading.WaitHandleCannotBeOpenedException_InvalidHandle", name));

                case OpenExistingResult.PathNotFound:
                    __Error.WinIOError(Win32Native.ERROR_PATH_NOT_FOUND, name);
                    return result; //never executes

                default:
                    return result;
            }
        }

        [System.Security.SecurityCritical]  // auto-generated_required
        [ResourceExposure(ResourceScope.Machine)]
        [ResourceConsumption(ResourceScope.Machine)]
        public static bool TryOpenExisting(string name, out Mutex result)
        {
#if !FEATURE_MACL
            return OpenExistingWorker(name, (MutexRights)0, out result) == OpenExistingResult.Success;
#else // FEATURE_PAL
            return OpenExistingWorker(name, MutexRights.Modify | MutexRights.Synchronize, out result) == OpenExistingResult.Success;
#endif // FEATURE_PAL
        }

        [System.Security.SecurityCritical]  // auto-generated_required
        [ResourceExposure(ResourceScope.Machine)]
        [ResourceConsumption(ResourceScope.Machine)]
        public static bool TryOpenExisting(string name, MutexRights rights, out Mutex result)
        {
            return OpenExistingWorker(name, rights, out result) == OpenExistingResult.Success;
        }

        [System.Security.SecurityCritical]
        [ResourceExposure(ResourceScope.Machine)]
        [ResourceConsumption(ResourceScope.Machine)]
        private static OpenExistingResult OpenExistingWorker(string name, MutexRights rights, out Mutex result)
        {
            if (name == null)
            {
                throw new ArgumentNullException("name", Environment.GetResourceString("ArgumentNull_WithParamName"));
            }

            if(name.Length  == 0)
            {
                throw new ArgumentException(Environment.GetResourceString("Argument_EmptyName"), "name");
            }
            if(System.IO.Path.MAX_PATH < name.Length)
            {
                throw new ArgumentException(Environment.GetResourceString("Argument_WaitHandleNameTooLong",name));
            }
            Contract.EndContractBlock();

            result = null;

#if FEATURE_LEGACYNETCF
            if (CompatibilitySwitches.IsAppEarlierThanWindowsPhone8)
                name = WinCEObjectNameQuirk(name);
#endif

            // To allow users to view & edit the ACL's, call OpenMutex
            // with parameters to allow us to view & edit the ACL.  This will
            // fail if we don't have permission to view or edit the ACL's.  
            // If that happens, ask for less permissions.
#if FEATURE_MACL
            SafeWaitHandle myHandle = Win32Native.OpenMutex((int) rights, false, name);
#else
            SafeWaitHandle myHandle = Win32Native.OpenMutex(Win32Native.MUTEX_MODIFY_STATE | Win32Native.SYNCHRONIZE, false, name);
#endif

            int errorCode = 0;
            if (myHandle.IsInvalid)
            {
                errorCode = Marshal.GetLastWin32Error();

                if(Win32Native.ERROR_FILE_NOT_FOUND == errorCode || Win32Native.ERROR_INVALID_NAME == errorCode)
                    return OpenExistingResult.NameNotFound;
                if (Win32Native.ERROR_PATH_NOT_FOUND == errorCode)
                    return OpenExistingResult.PathNotFound;
                if (null != name && 0 != name.Length && Win32Native.ERROR_INVALID_HANDLE == errorCode) 
                    return OpenExistingResult.NameInvalid;

                // this is for passed through Win32Native Errors
                __Error.WinIOError(errorCode,name);
            }

            result = new Mutex(myHandle);
            return OpenExistingResult.Success;
        }

        // Note: To call ReleaseMutex, you must have an ACL granting you
        // MUTEX_MODIFY_STATE rights (0x0001).  The other interesting value
        // in a Mutex's ACL is MUTEX_ALL_ACCESS (0x1F0001).
        [System.Security.SecuritySafeCritical]  // auto-generated
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]        
        public void ReleaseMutex()
        {
            if (Win32Native.ReleaseMutex(safeWaitHandle))
            {
#if !FEATURE_CORECLR
                Thread.EndCriticalRegion();
                Thread.EndThreadAffinity();
#endif
            }
            else
            {
#if FEATURE_CORECLR
                throw new Exception(Environment.GetResourceString("Arg_SynchronizationLockException"));
#else
                throw new ApplicationException(Environment.GetResourceString("Arg_SynchronizationLockException"));
#endif // FEATURE_CORECLR
            }                                                               
        }

        [System.Security.SecurityCritical]  // auto-generated
        [ResourceExposure(ResourceScope.Machine)]
        [ResourceConsumption(ResourceScope.Machine)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        static int CreateMutexHandle(bool initiallyOwned, String name, Win32Native.SECURITY_ATTRIBUTES securityAttribute, out SafeWaitHandle mutexHandle) {            
            int errorCode;  
            bool fAffinity = false;
            
            while(true) {
                mutexHandle = Win32Native.CreateMutex(securityAttribute, initiallyOwned, name);
                errorCode = Marshal.GetLastWin32Error();                                
                if( !mutexHandle.IsInvalid) {
                    break;                
                }

                if( errorCode == Win32Native.ERROR_ACCESS_DENIED) {
                    // If a mutex with the name already exists, OS will try to open it with FullAccess.
                    // It might fail if we don't have enough access. In that case, we try to open the mutex will modify and synchronize access.
                    //
                    
                    RuntimeHelpers.PrepareConstrainedRegions();
                    try 
                    {
                        try 
                        {
                        } 
                        finally 
                        {
#if !FEATURE_CORECLR
                            Thread.BeginThreadAffinity();
#endif
                            fAffinity = true;
                        }
                        mutexHandle = Win32Native.OpenMutex(Win32Native.MUTEX_MODIFY_STATE | Win32Native.SYNCHRONIZE, false, name);
                        if(!mutexHandle.IsInvalid)
                        {
                            errorCode = Win32Native.ERROR_ALREADY_EXISTS;
                        }
                        else
                        {
                            errorCode = Marshal.GetLastWin32Error();
                        }
                    }
                    finally 
                    {
                        if (fAffinity) {
#if !FEATURE_CORECLR
                            Thread.EndThreadAffinity();
#endif
                        }
                    }

                    // There could be a ---- here, the other owner of the mutex can free the mutex,
                    // We need to retry creation in that case.
                    if( errorCode != Win32Native.ERROR_FILE_NOT_FOUND) {
                        if( errorCode == Win32Native.ERROR_SUCCESS) {
                            errorCode =  Win32Native.ERROR_ALREADY_EXISTS;
                        }                        
                        break;
                    }
                }
                else {
                    break;
                }
            }                        
            return errorCode;
        }
        
#if FEATURE_MACL
        [System.Security.SecuritySafeCritical]  // auto-generated
        public MutexSecurity GetAccessControl()
        {
            return new MutexSecurity(safeWaitHandle, AccessControlSections.Access | AccessControlSections.Owner | AccessControlSections.Group);
        }

        [System.Security.SecuritySafeCritical]  // auto-generated
        public void SetAccessControl(MutexSecurity mutexSecurity)
        {
            if (mutexSecurity == null)
                throw new ArgumentNullException("mutexSecurity");
            Contract.EndContractBlock();

            mutexSecurity.Persist(safeWaitHandle);
        }
#endif

    }
}
