Port ACL OpenExisting overloads for EventWaitHandle/Mutex/Semaphore (#43134)
authorCarlos Sanchez <1175054+carlossanlop@users.noreply.github.com>
Sat, 17 Oct 2020 00:12:30 +0000 (17:12 -0700)
committerGitHub <noreply@github.com>
Sat, 17 Oct 2020 00:12:30 +0000 (17:12 -0700)
* Add methods to ref file.

* Add empty methods to src files.

* Add the .NET Framework version of these methods for .NET Standard.

* Move OpenExistingResult enum to Common and consume it where needed.

* Remove Unix comment in Windows-only file.

* Document OpenExistingResult enum.

* Make out result parameters nullable.

* Add exception resource string.

* Imlement EventWaitHandleAcl methods.

* Implement MutexAcl methods.

* Implement SemaphoreAcl methods.

* Remove unnecessary check for null or empty name.

* Document the EventWaitHandleAcl methods.

* Document the MutexAcl methods.

* Document the SemaphoreAcl methods.

* Add negative enum check. Fix incorrect cast.

* Add NotNullWhen attribute to TryOpenExisting out parameter. Adjust docs.

* Add EventWaitHandle basic unit tests.

* Add Semaphore basic unit tests

* Add Mutex basic unit tests.

* Add Mutex exception handling unit tests.

* Add EventWaitHandle exception handling unit tests.

* Add Semaphore exception handling unit tests.

* EventWaitHandle and Mutex throw DirectoryNotFoundException when PathNotFound. Adjust documentation.

* Nullability in ref file.

* Do not check for rights out of range value, let Windows handle it. Adjust unit tests accordingly.

* Spacing.

* Remove enum range test. Add PathNotFound tests.

Co-authored-by: Carlos Sanchez Lopez <carlossanlop@users.noreply.github.com>
17 files changed:
src/libraries/Common/src/System/Threading/OpenExistingResult.cs [new file with mode: 0644]
src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
src/libraries/System.Private.CoreLib/src/System/Threading/EventWaitHandle.Windows.cs
src/libraries/System.Private.CoreLib/src/System/Threading/Semaphore.Windows.cs
src/libraries/System.Private.CoreLib/src/System/Threading/WaitHandle.cs
src/libraries/System.Threading.AccessControl/ref/System.Threading.AccessControl.cs
src/libraries/System.Threading.AccessControl/src/Resources/Strings.resx
src/libraries/System.Threading.AccessControl/src/System.Threading.AccessControl.csproj
src/libraries/System.Threading.AccessControl/src/System/Threading/EventWaitHandleAcl.cs
src/libraries/System.Threading.AccessControl/src/System/Threading/EventWaitHandleAcl.net46.cs
src/libraries/System.Threading.AccessControl/src/System/Threading/MutexAcl.cs
src/libraries/System.Threading.AccessControl/src/System/Threading/MutexAcl.net46.cs
src/libraries/System.Threading.AccessControl/src/System/Threading/SemaphoreAcl.cs
src/libraries/System.Threading.AccessControl/src/System/Threading/SemaphoreAcl.net46.cs
src/libraries/System.Threading.AccessControl/tests/EventWaitHandleAclTests.cs
src/libraries/System.Threading.AccessControl/tests/MutexAclTests.cs
src/libraries/System.Threading.AccessControl/tests/SemaphoreAclTests.cs

diff --git a/src/libraries/Common/src/System/Threading/OpenExistingResult.cs b/src/libraries/Common/src/System/Threading/OpenExistingResult.cs
new file mode 100644 (file)
index 0000000..16d9aba
--- /dev/null
@@ -0,0 +1,17 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.Threading
+{
+    /// <summary>
+    /// Mapping of the different success or failure values that can occur when p/invoking Interop.Kernel32.OpenEvent,
+    /// which is called inside the OpenExistingWorker methods defined in EventWaitHandle, Mutex and Semaphore.
+    /// </summary>
+    internal enum OpenExistingResult
+    {
+        Success,
+        NameNotFound,
+        PathNotFound,
+        NameInvalid
+    }
+}
index 4ac94b0..8c2111e 100644 (file)
     <Compile Include="$(CommonPath)System\Text\ValueStringBuilder.cs">
       <Link>Common\System\Text\ValueStringBuilder.cs</Link>
     </Compile>
+    <Compile Include="$(CommonPath)System\Threading\OpenExistingResult.cs">
+      <Link>Common\System\Threading\OpenExistingResult.cs</Link>
+    </Compile>
     <Compile Include="$(CommonPath)System\Threading\Tasks\TaskToApm.cs">
       <Link>Common\System\Threading\Tasks\TaskToApm.cs</Link>
     </Compile>
index 4c142bc..73256bc 100644 (file)
@@ -60,7 +60,7 @@ namespace System.Threading
                     return OpenExistingResult.NameNotFound;
                 if (errorCode == Interop.Errors.ERROR_PATH_NOT_FOUND)
                     return OpenExistingResult.PathNotFound;
-                if (!string.IsNullOrEmpty(name) && errorCode == Interop.Errors.ERROR_INVALID_HANDLE)
+                if (errorCode == Interop.Errors.ERROR_INVALID_HANDLE)
                     return OpenExistingResult.NameInvalid;
 
                 throw Win32Marshal.GetExceptionForWin32Error(errorCode, name);
index d96882e..9cef42f 100644 (file)
@@ -62,7 +62,7 @@ namespace System.Threading
                     return OpenExistingResult.NameNotFound;
                 if (errorCode == Interop.Errors.ERROR_PATH_NOT_FOUND)
                     return OpenExistingResult.PathNotFound;
-                if (!string.IsNullOrEmpty(name) && errorCode == Interop.Errors.ERROR_INVALID_HANDLE)
+                if (errorCode == Interop.Errors.ERROR_INVALID_HANDLE)
                     return OpenExistingResult.NameInvalid;
                 // this is for passed through NativeMethods Errors
                 throw Win32Marshal.GetExceptionForLastWin32Error();
index ee84050..9fcbf20 100644 (file)
@@ -21,14 +21,6 @@ namespace System.Threading
         [ThreadStatic]
         private static SafeWaitHandle?[]? t_safeWaitHandlesForRent;
 
-        private protected enum OpenExistingResult
-        {
-            Success,
-            NameNotFound,
-            PathNotFound,
-            NameInvalid
-        }
-
         // The wait result values below match Win32 wait result codes (WAIT_OBJECT_0,
         // WAIT_ABANDONED, WAIT_TIMEOUT).
 
index b42eb33..b897c38 100644 (file)
@@ -140,14 +140,20 @@ namespace System.Threading
     public static partial class EventWaitHandleAcl
     {
         public static System.Threading.EventWaitHandle Create(bool initialState, System.Threading.EventResetMode mode, string? name, out bool createdNew, System.Security.AccessControl.EventWaitHandleSecurity? eventSecurity) { throw null; }
+        public static System.Threading.EventWaitHandle OpenExisting(string name, System.Security.AccessControl.EventWaitHandleRights rights) { throw null; }
+        public static bool TryOpenExisting(string name, System.Security.AccessControl.EventWaitHandleRights rights, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out System.Threading.EventWaitHandle? result) { throw null; }
     }
     public static partial class MutexAcl
     {
         public static System.Threading.Mutex Create(bool initiallyOwned, string? name, out bool createdNew, System.Security.AccessControl.MutexSecurity? mutexSecurity) { throw null; }
+        public static System.Threading.Mutex OpenExisting(string name, System.Security.AccessControl.MutexRights rights) { throw null; }
+        public static bool TryOpenExisting(string name, System.Security.AccessControl.MutexRights rights, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out System.Threading.Mutex? result) { throw null; }
     }
     public static partial class SemaphoreAcl
     {
         public static System.Threading.Semaphore Create(int initialCount, int maximumCount, string? name, out bool createdNew, System.Security.AccessControl.SemaphoreSecurity? semaphoreSecurity) { throw null; }
+        public static System.Threading.Semaphore OpenExisting(string name, System.Security.AccessControl.SemaphoreRights rights) { throw null; }
+        public static bool TryOpenExisting(string name, System.Security.AccessControl.SemaphoreRights rights, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out System.Threading.Semaphore? result) { throw null;  }
     }
     public static partial class ThreadingAclExtensions
     {
index f676b7b..3503004 100644 (file)
   <resheader name="writer">
     <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </resheader>
+  <data name="ArgumentOutOfRange_Enum" xml:space="preserve">
+    <value>Enum value was out of legal range.</value>
+  </data>
   <data name="ArgumentOutOfRange_NeedNonNegNum" xml:space="preserve">
     <value>Non-negative number required.</value>
   </data>
   <data name="ArgumentOutOfRange_NeedPosNum" xml:space="preserve">
     <value>Positive number required.</value>
   </data>
-  <data name="Argument_SemaphoreInitialMaximum" xml:space="preserve">
-    <value>The initial count for the semaphore must be greater than or equal to zero and less than the maximum count.</value>
-  </data>
   <data name="Argument_CannotBeNullOrEmpty" xml:space="preserve">
     <value>Argument cannot be null or empty.</value>
   </data>
+  <data name="Argument_EmptyName" xml:space="preserve">
+    <value>Empty name is not legal.</value>
+  </data>
+  <data name="Argument_SemaphoreInitialMaximum" xml:space="preserve">
+    <value>The initial count for the semaphore must be greater than or equal to zero and less than the maximum count.</value>
+  </data>
   <data name="Argument_WaitHandleNameTooLong" xml:space="preserve">
     <value>The length of the name exceeds the maximum limit.</value>
   </data>
-  <data name="ArgumentOutOfRange_Enum" xml:space="preserve">
-    <value>Enum value was out of legal range.</value>
-  </data>
   <data name="IO_AlreadyExists_Name" xml:space="preserve">
     <value>Cannot create '{0}' because a file or directory with the same name already exists.</value>
   </data>
   <data name="WaitHandleCannotBeOpenedException_InvalidHandle" xml:space="preserve">
     <value>A WaitHandle with system-wide name '{0}' cannot be created. A WaitHandle of a different type might have the same name.</value>
   </data>
-</root>
\ No newline at end of file
+</root>
index df22a86..5a898d9 100644 (file)
@@ -34,6 +34,8 @@
              Link="Interop\Windows\Kernel32\Interop.Semaphore.cs" />
     <Compile Include="$(CommonPath)System\IO\Win32Marshal.cs"
              Link="Common\System\IO\Win32Marshal.cs" />
+    <Compile Include="$(CommonPath)System\Threading\OpenExistingResult.cs"
+             Link="Common\System\Threading\OpenExistingResult.cs" />
     <Compile Include="System\Security\AccessControl\MutexSecurity.cs" />
     <Compile Include="System\Security\AccessControl\EventWaitHandleSecurity.cs" />
     <Compile Include="System\Security\AccessControl\SemaphoreSecurity.cs" />
index 036a7eb..e344db4 100644 (file)
@@ -1,6 +1,8 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
 using System.IO;
 using System.Runtime.InteropServices;
 using System.Security.AccessControl;
@@ -56,32 +58,115 @@ namespace System.Threading
                     eventFlags,
                     (uint)EventWaitHandleRights.FullControl);
 
-                ValidateHandle(handle, name, out createdNew);
+                int errorCode = Marshal.GetLastWin32Error();
 
-                EventWaitHandle ewh = new EventWaitHandle(initialState, mode);
-                SafeWaitHandle old = ewh.SafeWaitHandle;
-                ewh.SafeWaitHandle = handle;
-                old.Dispose();
+                if (handle.IsInvalid)
+                {
+                    handle.SetHandleAsInvalid();
+
+                    if (!string.IsNullOrEmpty(name) && errorCode == Interop.Errors.ERROR_INVALID_HANDLE)
+                    {
+                        throw new WaitHandleCannotBeOpenedException(SR.Format(SR.WaitHandleCannotBeOpenedException_InvalidHandle, name));
+                    }
+
+                    throw Win32Marshal.GetExceptionForWin32Error(errorCode, name);
+                }
+
+                createdNew = (errorCode != Interop.Errors.ERROR_ALREADY_EXISTS);
 
-                return ewh;
+                return CreateAndReplaceHandle(handle);
             }
         }
 
-        private static void ValidateHandle(SafeWaitHandle handle, string? name, out bool createdNew)
+        /// <summary>
+        /// Opens a specified named event wait handle, if it already exists, applying the desired access rights.
+        /// </summary>
+        /// <param name="name">The name of the event wait handle to be opened. If it's prefixed by "Global", it refers to a machine-wide event wait handle. If it's prefixed by "Local", or doesn't have a prefix, it refers to a session-wide event wait handle. Both prefix and name are case-sensitive.</param>
+        /// <param name="rights">The desired access rights to apply to the returned event wait handle.</param>
+        /// <returns>An existing named event wait handle.</returns>
+        /// <exception cref="ArgumentNullException"><paramref name="name"/> is <see langword="null" />.</exception>
+        /// <exception cref="ArgumentException"><paramref name="name"/> is an empty string.</exception>
+        /// <exception cref="WaitHandleCannotBeOpenedException">The named event wait handle does not exist or is invalid.</exception>
+        /// <exception cref="DirectoryNotFoundException">The path was not found.</exception>
+        /// <exception cref="IOException">A Win32 error occurred.</exception>
+        /// <exception cref="UnauthorizedAccessException">The named event wait handle exists, but the user does not have the security access required to use it.</exception>
+        public static EventWaitHandle OpenExisting(string name, EventWaitHandleRights rights)
         {
-            int errorCode = Marshal.GetLastWin32Error();
+            switch (OpenExistingWorker(name, rights, out EventWaitHandle? result))
+            {
+                case OpenExistingResult.NameNotFound:
+                    throw new WaitHandleCannotBeOpenedException();
+
+                case OpenExistingResult.NameInvalid:
+                    throw new WaitHandleCannotBeOpenedException(SR.Format(SR.Threading_WaitHandleCannotBeOpenedException_InvalidHandle, name));
 
-            if (handle.IsInvalid)
+                case OpenExistingResult.PathNotFound:
+                    throw new DirectoryNotFoundException(SR.Format(SR.IO_PathNotFound_Path, name));
+
+                case OpenExistingResult.Success:
+                default:
+                    Debug.Assert(result != null, "result should be non-null on success");
+                    return result;
+            }
+        }
+
+        /// <summary>
+        /// Tries to open a specified named event wait handle, if it already exists, applying the desired access rights, and returns a value that indicates whether the operation succeeded.
+        /// </summary>
+        /// <param name="name">The name of the event wait handle to be opened. If it's prefixed by "Global", it refers to a machine-wide event wait handle. If it's prefixed by "Local", or doesn't have a prefix, it refers to a session-wide event wait handle. Both prefix and name are case-sensitive.</param>
+        /// <param name="rights">The desired access rights to apply to the returned event wait handle.</param>
+        /// <param name="result">When this method returns <see langword="true" />, contains an object that represents the named event wait handle if the call succeeded, or <see langword="null" /> otherwise. This parameter is treated as uninitialized.</param>
+        /// <returns><see langword="true" /> if the named event wait handle was opened successfully; otherwise, <see langword="false" />.</returns>
+        /// <exception cref="ArgumentNullException"><paramref name="name"/> is <see langword="null" /></exception>
+        /// <exception cref="ArgumentException"><paramref name="name"/> is an empty string.</exception>
+        /// <exception cref="IOException">A Win32 error occurred.</exception>
+        /// <exception cref="UnauthorizedAccessException">The named event wait handle exists, but the user does not have the security access required to use it.</exception>
+        public static bool TryOpenExisting(string name, EventWaitHandleRights rights, [NotNullWhen(returnValue: true)] out EventWaitHandle? result) =>
+            OpenExistingWorker(name, rights, out result) == OpenExistingResult.Success;
+
+        private static OpenExistingResult OpenExistingWorker(string name, EventWaitHandleRights rights, out EventWaitHandle? result)
+        {
+            if (name == null)
             {
-                handle.SetHandleAsInvalid();
+                throw new ArgumentNullException(nameof(name));
+            }
+            if (name.Length == 0)
+            {
+                throw new ArgumentException(SR.Argument_EmptyName, nameof(name));
+            }
 
-                if (!string.IsNullOrEmpty(name) && errorCode == Interop.Errors.ERROR_INVALID_HANDLE)
-                    throw new WaitHandleCannotBeOpenedException(SR.Format(SR.WaitHandleCannotBeOpenedException_InvalidHandle, name));
+            result = null;
+            SafeWaitHandle existingHandle = Interop.Kernel32.OpenEvent((uint)rights, false, name);
 
-                throw Win32Marshal.GetExceptionForWin32Error(errorCode, name);
+            int errorCode = Marshal.GetLastWin32Error();
+            if (existingHandle.IsInvalid)
+            {
+                return errorCode switch
+                {
+                    Interop.Errors.ERROR_FILE_NOT_FOUND or Interop.Errors.ERROR_INVALID_NAME => OpenExistingResult.NameNotFound,
+                    Interop.Errors.ERROR_PATH_NOT_FOUND => OpenExistingResult.PathNotFound,
+                    Interop.Errors.ERROR_INVALID_HANDLE => OpenExistingResult.NameInvalid,
+                    _ => throw Win32Marshal.GetExceptionForWin32Error(errorCode, name)
+                };
             }
 
-            createdNew = (errorCode != Interop.Errors.ERROR_ALREADY_EXISTS);
+            result = CreateAndReplaceHandle(existingHandle);
+
+            return OpenExistingResult.Success;
+        }
+
+        private static EventWaitHandle CreateAndReplaceHandle(SafeWaitHandle replacementHandle)
+        {
+            // The values of initialState and mode should not matter since we are replacing the
+            // handle with one from an existing EventWaitHandle, and disposing the old one
+            // We should only make sure that they are valid values
+            EventWaitHandle eventWaitHandle = new EventWaitHandle(initialState: default, mode: default);
+
+            SafeWaitHandle old = eventWaitHandle.SafeWaitHandle;
+            eventWaitHandle.SafeWaitHandle = replacementHandle;
+            old.Dispose();
+
+            return eventWaitHandle;
         }
     }
 }
index 0cdc012..a1bb177 100644 (file)
@@ -16,5 +16,15 @@ namespace System.Threading
         {
             return new EventWaitHandle(initialState, mode, name, out createdNew, eventSecurity);
         }
+
+        public static EventWaitHandle OpenExisting(string name, EventWaitHandleRights rights)
+        {
+            return EventWaitHandle.OpenExisting(name, rights);
+        }
+
+        public static bool TryOpenExisting(string name, EventWaitHandleRights rights, out EventWaitHandle result)
+        {
+            return EventWaitHandle.TryOpenExisting(name, rights, out result);
+        }
     }
 }
index f289242..b19a310 100644 (file)
@@ -1,6 +1,8 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
 using System.IO;
 using System.Runtime.InteropServices;
 using System.Security.AccessControl;
@@ -42,40 +44,120 @@ namespace System.Threading
                     (uint)MutexRights.FullControl // Equivalent to MUTEX_ALL_ACCESS
                 );
 
-                ValidateMutexHandle(handle, name, out createdNew);
+                int errorCode = Marshal.GetLastWin32Error();
 
-                Mutex mutex = new Mutex(initiallyOwned);
-                SafeWaitHandle old = mutex.SafeWaitHandle;
-                mutex.SafeWaitHandle = handle;
-                old.Dispose();
+                if (handle.IsInvalid)
+                {
+                    handle.SetHandleAsInvalid();
+
+                    if (errorCode == Interop.Errors.ERROR_FILENAME_EXCED_RANGE)
+                    {
+                        throw new ArgumentException(SR.Argument_WaitHandleNameTooLong, nameof(name));
+                    }
+
+                    if (errorCode == Interop.Errors.ERROR_INVALID_HANDLE)
+                    {
+                        throw new WaitHandleCannotBeOpenedException(SR.Format(SR.Threading_WaitHandleCannotBeOpenedException_InvalidHandle, name));
+                    }
+
+                    throw Win32Marshal.GetExceptionForWin32Error(errorCode, name);
+                }
 
-                return mutex;
+                createdNew = (errorCode != Interop.Errors.ERROR_ALREADY_EXISTS);
+
+                return CreateAndReplaceHandle(handle);
             }
         }
 
-        private static void ValidateMutexHandle(SafeWaitHandle mutexHandle, string? name, out bool createdNew)
+        /// <summary>
+        /// Opens a specified named mutex, if it already exists, applying the desired access rights.
+        /// </summary>
+        /// <param name="name">The name of the mutex to be opened. If it's prefixed by "Global", it refers to a machine-wide mutex. If it's prefixed by "Local", or doesn't have a prefix, it refers to a session-wide mutex. Both prefix and name are case-sensitive.</param>
+        /// <param name="rights">The desired access rights to apply to the returned mutex.</param>
+        /// <returns>An existing named mutex.</returns>
+        /// <exception cref="ArgumentNullException"><paramref name="name"/> is <see langword="null" />.</exception>
+        /// <exception cref="ArgumentException"><paramref name="name"/> is an empty string.</exception>
+        /// <exception cref="WaitHandleCannotBeOpenedException">The named mutex does not exist or is invalid.</exception>
+        /// <exception cref="DirectoryNotFoundException">The path was not found.</exception>
+        /// <exception cref="IOException">A Win32 error occurred.</exception>
+        /// <exception cref="UnauthorizedAccessException">The named mutex exists, but the user does not have the security access required to use it.</exception>
+        public static Mutex OpenExisting(string name, MutexRights rights)
         {
-            int errorCode = Marshal.GetLastWin32Error();
+            switch (OpenExistingWorker(name, rights, out Mutex? result))
+            {
+                case OpenExistingResult.NameNotFound:
+                    throw new WaitHandleCannotBeOpenedException();
 
-            if (mutexHandle.IsInvalid)
+                case OpenExistingResult.NameInvalid:
+                    throw new WaitHandleCannotBeOpenedException(SR.Format(SR.Threading_WaitHandleCannotBeOpenedException_InvalidHandle, name));
+
+                case OpenExistingResult.PathNotFound:
+                    throw new DirectoryNotFoundException(SR.Format(SR.IO_PathNotFound_Path, name));
+
+                case OpenExistingResult.Success:
+                default:
+                    Debug.Assert(result != null, "result should be non-null on success");
+                    return result;
+            }
+        }
+
+        /// <summary>
+        /// Tries to open a specified named mutex, if it already exists, applying the desired access rights, and returns a value that indicates whether the operation succeeded.
+        /// </summary>
+        /// <param name="name">The name of the mutex to be opened. If it's prefixed by "Global", it refers to a machine-wide mutex. If it's prefixed by "Local", or doesn't have a prefix, it refers to a session-wide mutex. Both prefix and name are case-sensitive.</param>
+        /// <param name="rights">The desired access rights to apply to the returned mutex.</param>
+        /// <param name="result">When this method returns <see langword="true" />, contains an object that represents the named mutex if the call succeeded, or <see langword="null" /> otherwise. This parameter is treated as uninitialized.</param>
+        /// <returns><see langword="true" /> if the named mutex was opened successfully; otherwise, <see langword="false" />.</returns>
+        /// <exception cref="ArgumentNullException"><paramref name="name"/> is <see langword="null" /></exception>
+        /// <exception cref="ArgumentException"><paramref name="name"/> is an empty string.</exception>
+        /// <exception cref="IOException">A Win32 error occurred.</exception>
+        /// <exception cref="UnauthorizedAccessException">The named mutex exists, but the user does not have the security access required to use it.</exception>
+        public static bool TryOpenExisting(string name, MutexRights rights, [NotNullWhen(returnValue: true)] out Mutex? result) =>
+            OpenExistingWorker(name, rights, out result) == OpenExistingResult.Success;
+
+        private static OpenExistingResult OpenExistingWorker(string name, MutexRights rights, out Mutex? result)
+        {
+            if (name == null)
             {
-                mutexHandle.SetHandleAsInvalid();
+                throw new ArgumentNullException(nameof(name));
+            }
+            if (name.Length == 0)
+            {
+                throw new ArgumentException(SR.Argument_EmptyName, nameof(name));
+            }
 
-                if (errorCode == Interop.Errors.ERROR_FILENAME_EXCED_RANGE)
-                {
-                    // On Unix, length validation is done by CoreCLR's PAL after converting to utf-8
-                    throw new ArgumentException(SR.Argument_WaitHandleNameTooLong, nameof(name));
-                }
+            result = null;
+            SafeWaitHandle existingHandle = Interop.Kernel32.OpenMutex((uint)rights, false, name);
 
-                if (errorCode == Interop.Errors.ERROR_INVALID_HANDLE)
+            int errorCode = Marshal.GetLastWin32Error();
+            if (existingHandle.IsInvalid)
+            {
+                return errorCode switch
                 {
-                    throw new WaitHandleCannotBeOpenedException(SR.Format(SR.Threading_WaitHandleCannotBeOpenedException_InvalidHandle, name));
-                }
-
-                throw Win32Marshal.GetExceptionForWin32Error(errorCode, name);
+                    Interop.Errors.ERROR_FILE_NOT_FOUND or Interop.Errors.ERROR_INVALID_NAME => OpenExistingResult.NameNotFound,
+                    Interop.Errors.ERROR_PATH_NOT_FOUND => OpenExistingResult.PathNotFound,
+                    Interop.Errors.ERROR_INVALID_HANDLE => OpenExistingResult.NameInvalid,
+                    _ => throw Win32Marshal.GetExceptionForWin32Error(errorCode, name)
+                };
             }
 
-            createdNew = (errorCode != Interop.Errors.ERROR_ALREADY_EXISTS);
+            result = CreateAndReplaceHandle(existingHandle);
+
+            return OpenExistingResult.Success;
+        }
+
+        private static Mutex CreateAndReplaceHandle(SafeWaitHandle replacementHandle)
+        {
+            // The value of initiallyOwned should not matter since we are replacing the
+            // handle with one from an existing Mutex, and disposing the old one
+            // We should only make sure that it is a valid value
+            Mutex mutex = new Mutex(initiallyOwned: default);
+
+            SafeWaitHandle old = mutex.SafeWaitHandle;
+            mutex.SafeWaitHandle = replacementHandle;
+            old.Dispose();
+
+            return mutex;
         }
     }
 }
index 2c285a6..0cc8e1f 100644 (file)
@@ -11,5 +11,15 @@ namespace System.Threading
         {
             return new Mutex(initiallyOwned, name, out createdNew, mutexSecurity);
         }
+
+        public static Mutex OpenExisting(string name, MutexRights rights)
+        {
+            return Mutex.OpenExisting(name, rights);
+        }
+
+        public static bool TryOpenExisting(string name, MutexRights rights, out Mutex result)
+        {
+            return Mutex.TryOpenExisting(name, rights, out result);
+        }
     }
 }
index 35d6529..eee9206 100644 (file)
@@ -1,6 +1,8 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
 using System.IO;
 using System.Runtime.InteropServices;
 using System.Security.AccessControl;
@@ -61,32 +63,113 @@ namespace System.Threading
                     (uint)SemaphoreRights.FullControl // Equivalent to SEMAPHORE_ALL_ACCESS
                 );
 
-                ValidateHandle(handle, name, out createdNew);
+                int errorCode = Marshal.GetLastWin32Error();
 
-                Semaphore semaphore = new Semaphore(initialCount, maximumCount);
-                SafeWaitHandle old = semaphore.SafeWaitHandle;
-                semaphore.SafeWaitHandle = handle;
-                old.Dispose();
+                if (handle.IsInvalid)
+                {
+                    if (!string.IsNullOrEmpty(name) && errorCode == Interop.Errors.ERROR_INVALID_HANDLE)
+                    {
+                        throw new WaitHandleCannotBeOpenedException(SR.Format(SR.Threading_WaitHandleCannotBeOpenedException_InvalidHandle, name));
+                    }
+
+                    throw Win32Marshal.GetExceptionForLastWin32Error();
+                }
+
+                createdNew = (errorCode != Interop.Errors.ERROR_ALREADY_EXISTS);
 
-                return semaphore;
+                return CreateAndReplaceHandle(handle);
             }
         }
 
-        private static void ValidateHandle(SafeWaitHandle handle, string? name, out bool createdNew)
+        /// <summary>
+        /// Opens a specified named semaphore, if it already exists, applying the desired access rights.
+        /// </summary>
+        /// <param name="name">The name of the semaphore to be opened. If it's prefixed by "Global", it refers to a machine-wide semaphore. If it's prefixed by "Local", or doesn't have a prefix, it refers to a session-wide semaphore. Both prefix and name are case-sensitive.</param>
+        /// <param name="rights">The desired access rights to apply to the returned semaphore.</param>
+        /// <returns>An existing named semaphore.</returns>
+        /// <exception cref="ArgumentNullException"><paramref name="name"/> is <see langword="null" />.</exception>
+        /// <exception cref="ArgumentException"><paramref name="name"/> is an empty string.</exception>
+        /// <exception cref="WaitHandleCannotBeOpenedException">The named semaphore does not exist or is invalid.</exception>
+        /// <exception cref="IOException">The path was not found.
+        /// -or-
+        /// A Win32 error occurred.</exception>
+        /// <exception cref="UnauthorizedAccessException">The named semaphore exists, but the user does not have the security access required to use it.</exception>
+        public static Semaphore OpenExisting(string name, SemaphoreRights rights)
         {
-            int errorCode = Marshal.GetLastWin32Error();
+            switch (OpenExistingWorker(name, rights, out Semaphore? result))
+            {
+                case OpenExistingResult.NameNotFound:
+                    throw new WaitHandleCannotBeOpenedException();
+
+                case OpenExistingResult.NameInvalid:
+                    throw new WaitHandleCannotBeOpenedException(SR.Format(SR.Threading_WaitHandleCannotBeOpenedException_InvalidHandle, name));
+
+                case OpenExistingResult.PathNotFound:
+                    throw new IOException(SR.Format(SR.IO_PathNotFound_Path, name));
+
+                case OpenExistingResult.Success:
+                default:
+                    Debug.Assert(result != null, "result should be non-null on success");
+                    return result;
+            }
+        }
+
+        /// <summary>
+        /// Tries to open a specified named semaphore, if it already exists, applying the desired access rights, and returns a value that indicates whether the operation succeeded.
+        /// </summary>
+        /// <param name="name">The name of the semaphore to be opened. If it's prefixed by "Global", it refers to a machine-wide semaphore. If it's prefixed by "Local", or doesn't have a prefix, it refers to a session-wide semaphore. Both prefix and name are case-sensitive.</param>
+        /// <param name="rights">The desired access rights to apply to the returned semaphore.</param>
+        /// <param name="result">When this method returns <see langword="true" />, contains an object that represents the named semaphore if the call succeeded, or <see langword="null" /> otherwise. This parameter is treated as uninitialized.</param>
+        /// <returns><see langword="true" /> if the named semaphore was opened successfully; otherwise, <see langword="false" />.</returns>
+        /// <exception cref="ArgumentNullException"><paramref name="name"/> is <see langword="null" /></exception>
+        /// <exception cref="ArgumentException"><paramref name="name"/> is an empty string.</exception>
+        /// <exception cref="IOException">A Win32 error occurred.</exception>
+        /// <exception cref="UnauthorizedAccessException">The named semaphore exists, but the user does not have the security access required to use it.</exception>
+        public static bool TryOpenExisting(string name, SemaphoreRights rights, [NotNullWhen(returnValue: true)] out Semaphore? result) =>
+            OpenExistingWorker(name, rights, out result) == OpenExistingResult.Success;
 
+        private static OpenExistingResult OpenExistingWorker(string name, SemaphoreRights rights, out Semaphore? result)
+        {
+            if (name == null)
+            {
+                throw new ArgumentNullException(nameof(name));
+            }
+            if (name.Length == 0)
+            {
+                throw new ArgumentException(SR.Argument_EmptyName, nameof(name));
+            }
+
+            result = null;
+            SafeWaitHandle handle = Interop.Kernel32.OpenSemaphore((uint)rights, false, name);
+
+            int errorCode = Marshal.GetLastWin32Error();
             if (handle.IsInvalid)
             {
-                if (!string.IsNullOrEmpty(name) && errorCode == Interop.Errors.ERROR_INVALID_HANDLE)
+                return errorCode switch
                 {
-                    throw new WaitHandleCannotBeOpenedException(SR.Format(SR.Threading_WaitHandleCannotBeOpenedException_InvalidHandle, name));
-                }
-
-                throw Win32Marshal.GetExceptionForLastWin32Error();
+                    Interop.Errors.ERROR_FILE_NOT_FOUND or Interop.Errors.ERROR_INVALID_NAME => OpenExistingResult.NameNotFound,
+                    Interop.Errors.ERROR_PATH_NOT_FOUND => OpenExistingResult.PathNotFound,
+                    Interop.Errors.ERROR_INVALID_HANDLE => OpenExistingResult.NameInvalid,
+                    _ => throw Win32Marshal.GetExceptionForLastWin32Error()
+                };
             }
 
-            createdNew = errorCode != Interop.Errors.ERROR_ALREADY_EXISTS;
+            result = CreateAndReplaceHandle(handle);
+            return OpenExistingResult.Success;
+        }
+
+        private static Semaphore CreateAndReplaceHandle(SafeWaitHandle replacementHandle)
+        {
+            // The values of initialCount and maximumCount should not matter since we are replacing the
+            // handle with one from an existing Semaphore, and disposing the old one
+            // We should only make sure that they are valid values
+            Semaphore semaphore = new Semaphore(1, 2);
+
+            SafeWaitHandle old = semaphore.SafeWaitHandle;
+            semaphore.SafeWaitHandle = replacementHandle;
+            old.Dispose();
+
+            return semaphore;
         }
     }
 }
index 98b88ac..b4f6590 100644 (file)
@@ -16,5 +16,15 @@ namespace System.Threading
         {
             return new Semaphore(initialCount, maximumCount, name, out createdNew, semaphoreSecurity);
         }
+
+        public static Semaphore OpenExisting(string name, SemaphoreRights rights)
+        {
+            return Semaphore.OpenExisting(name, rights);
+        }
+
+        public static bool TryOpenExisting(string name, SemaphoreRights rights, out Semaphore result)
+        {
+            return Semaphore.TryOpenExisting(name, rights, out result);
+        }
     }
 }
index c6aca23..3e9324e 100644 (file)
@@ -177,6 +177,116 @@ namespace System.Threading.Tests
 
         }
 
+        [Fact]
+        public void EventWaitHandle_OpenExisting()
+        {
+            string name = GetRandomName();
+            EventWaitHandleSecurity expectedSecurity = GetEventWaitHandleSecurity(WellKnownSidType.BuiltinUsersSid, EventWaitHandleRights.FullControl, AccessControlType.Allow);
+            using EventWaitHandle EventWaitHandleNew = CreateAndVerifyEventWaitHandle(initialState: true, EventResetMode.AutoReset, name, expectedSecurity, expectedCreatedNew: true);
+
+            using EventWaitHandle EventWaitHandleExisting = EventWaitHandleAcl.OpenExisting(name, EventWaitHandleRights.FullControl);
+
+            VerifyHandles(EventWaitHandleNew, EventWaitHandleExisting);
+            EventWaitHandleSecurity actualSecurity = EventWaitHandleExisting.GetAccessControl();
+            VerifyEventWaitHandleSecurity(expectedSecurity, actualSecurity);
+        }
+
+        [Fact]
+        public void EventWaitHandle_TryOpenExisting()
+        {
+            string name = GetRandomName();
+            EventWaitHandleSecurity expectedSecurity = GetEventWaitHandleSecurity(WellKnownSidType.BuiltinUsersSid, EventWaitHandleRights.FullControl, AccessControlType.Allow);
+            using EventWaitHandle EventWaitHandleNew = CreateAndVerifyEventWaitHandle(initialState: true, EventResetMode.AutoReset, name, expectedSecurity, expectedCreatedNew: true);
+
+            Assert.True(EventWaitHandleAcl.TryOpenExisting(name, EventWaitHandleRights.FullControl, out EventWaitHandle EventWaitHandleExisting));
+            Assert.NotNull(EventWaitHandleExisting);
+
+            VerifyHandles(EventWaitHandleNew, EventWaitHandleExisting);
+            EventWaitHandleSecurity actualSecurity = EventWaitHandleExisting.GetAccessControl();
+            VerifyEventWaitHandleSecurity(expectedSecurity, actualSecurity);
+
+            EventWaitHandleExisting.Dispose();
+        }
+
+        [Fact]
+        public void EventWaitHandle_OpenExisting_NameNotFound()
+        {
+            string name = "ThisShouldNotExist";
+            Assert.Throws<WaitHandleCannotBeOpenedException>(() =>
+            {
+                EventWaitHandleAcl.OpenExisting(name, EventWaitHandleRights.FullControl).Dispose();
+            });
+
+            Assert.False(EventWaitHandleAcl.TryOpenExisting(name, EventWaitHandleRights.FullControl, out _));
+        }
+
+        [Fact]
+        public void EventWaitHandle_OpenExisting_NameInvalid()
+        {
+            string name = '\0'.ToString();
+            Assert.Throws<WaitHandleCannotBeOpenedException>(() =>
+            {
+                EventWaitHandleAcl.OpenExisting(name, EventWaitHandleRights.FullControl).Dispose();
+            });
+
+            Assert.False(EventWaitHandleAcl.TryOpenExisting(name, EventWaitHandleRights.FullControl, out _));
+        }
+
+        [Fact]
+        public void EventWaitHandle_OpenExisting_PathNotFound()
+        {
+            string name = @"global\foo";
+            Assert.Throws<DirectoryNotFoundException>(() =>
+            {
+                EventWaitHandleAcl.OpenExisting(name, EventWaitHandleRights.FullControl).Dispose();
+            });
+
+            Assert.False(EventWaitHandleAcl.TryOpenExisting(name, EventWaitHandleRights.FullControl, out _));
+        }
+
+        [Fact]
+        public void EventWaitHandle_OpenExisting_BadPathName()
+        {
+            string name = @"\\?\Path";
+            Assert.Throws<System.IO.IOException>(() =>
+            {
+                EventWaitHandleAcl.OpenExisting(name, EventWaitHandleRights.FullControl).Dispose();
+            });
+
+            Assert.Throws<System.IO.IOException>(() =>
+            {
+                EventWaitHandleAcl.TryOpenExisting(name, EventWaitHandleRights.FullControl, out _);
+            });
+        }
+
+        [Fact]
+        public void EventWaitHandle_OpenExisting_NullName()
+        {
+            Assert.Throws<ArgumentNullException>(() =>
+            {
+                EventWaitHandleAcl.OpenExisting(null, EventWaitHandleRights.FullControl).Dispose();
+            });
+
+            Assert.Throws<ArgumentNullException>(() =>
+            {
+                EventWaitHandleAcl.TryOpenExisting(null, EventWaitHandleRights.FullControl, out _);
+            });
+        }
+
+        [Fact]
+        public void EventWaitHandle_OpenExisting_EmptyName()
+        {
+            Assert.Throws<ArgumentException>(() =>
+            {
+                EventWaitHandleAcl.OpenExisting(string.Empty, EventWaitHandleRights.FullControl).Dispose();
+            });
+
+            Assert.Throws<ArgumentException>(() =>
+            {
+                EventWaitHandleAcl.TryOpenExisting(string.Empty, EventWaitHandleRights.FullControl, out _);
+            });
+        }
+
         private EventWaitHandleSecurity GetBasicEventWaitHandleSecurity()
         {
             return GetEventWaitHandleSecurity(
@@ -193,6 +303,7 @@ namespace System.Threading.Tests
             security.AddAccessRule(accessRule);
             return security;
         }
+
         private EventWaitHandle CreateEventWaitHandle(bool initialState, EventResetMode mode, string name, EventWaitHandleSecurity expectedSecurity, bool expectedCreatedNew)
         {
             EventWaitHandle handle = EventWaitHandleAcl.Create(initialState, mode, name, out bool createdNew, expectedSecurity);
@@ -214,6 +325,18 @@ namespace System.Threading.Tests
             return eventHandle;
         }
 
+        private void VerifyHandles(EventWaitHandle expected, EventWaitHandle actual)
+        {
+            Assert.NotNull(expected.SafeWaitHandle);
+            Assert.NotNull(actual.SafeWaitHandle);
+
+            Assert.False(expected.SafeWaitHandle.IsClosed);
+            Assert.False(actual.SafeWaitHandle.IsClosed);
+
+            Assert.False(expected.SafeWaitHandle.IsInvalid);
+            Assert.False(actual.SafeWaitHandle.IsInvalid);
+        }
+
         private void VerifyEventWaitHandleSecurity(EventWaitHandleSecurity expectedSecurity, EventWaitHandleSecurity actualSecurity)
         {
             Assert.Equal(typeof(EventWaitHandleRights), expectedSecurity.AccessRightType);
index 218c117..3f90570 100644 (file)
@@ -2,6 +2,7 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System.Collections.Generic;
+using System.IO;
 using System.Linq;
 using System.Security.AccessControl;
 using System.Security.Principal;
@@ -59,26 +60,128 @@ namespace System.Threading.Tests
             }
         }
 
+        public static IEnumerable<object[]> GetMutexSpecificParameters() =>
+            from initiallyOwned in new[] { false, true }
+            from rights in new[] { MutexRights.FullControl, MutexRights.Synchronize, MutexRights.Modify, MutexRights.Modify | MutexRights.Synchronize }
+            from accessControl in new[] { AccessControlType.Allow, AccessControlType.Deny }
+            select new object[] { initiallyOwned, rights, accessControl };
+
         [Theory]
-        [InlineData(true, MutexRights.FullControl, AccessControlType.Allow)]
-        [InlineData(true, MutexRights.FullControl, AccessControlType.Deny)]
-        [InlineData(true, MutexRights.Synchronize, AccessControlType.Allow)]
-        [InlineData(true, MutexRights.Synchronize, AccessControlType.Deny)]
-        [InlineData(true, MutexRights.Modify, AccessControlType.Allow)]
-        [InlineData(true, MutexRights.Modify, AccessControlType.Deny)]
-        [InlineData(true, MutexRights.Modify | MutexRights.Synchronize, AccessControlType.Allow)]
-        [InlineData(true, MutexRights.Modify | MutexRights.Synchronize, AccessControlType.Deny)]
-        [InlineData(false, MutexRights.FullControl, AccessControlType.Allow)]
-        [InlineData(false, MutexRights.FullControl, AccessControlType.Deny)]
-        [InlineData(false, MutexRights.Synchronize, AccessControlType.Allow)]
-        [InlineData(false, MutexRights.Synchronize, AccessControlType.Deny)]
-        [InlineData(false, MutexRights.Modify, AccessControlType.Allow)]
-        [InlineData(false, MutexRights.Modify, AccessControlType.Deny)]
+        [MemberData(nameof(GetMutexSpecificParameters))]
         public void Mutex_Create_SpecificParameters(bool initiallyOwned, MutexRights rights, AccessControlType accessControl)
         {
             MutexSecurity security = GetMutexSecurity(WellKnownSidType.BuiltinUsersSid, rights, accessControl);
             CreateAndVerifyMutex(initiallyOwned, GetRandomName(), security, expectedCreatedNew: true).Dispose();
+        }
+
+        [Fact]
+        public void Mutex_OpenExisting()
+        {
+            string name = GetRandomName();
+            MutexSecurity expectedSecurity = GetMutexSecurity(WellKnownSidType.BuiltinUsersSid, MutexRights.FullControl, AccessControlType.Allow);
+            using Mutex mutexNew = CreateAndVerifyMutex(initiallyOwned: true, name, expectedSecurity, expectedCreatedNew: true);
+
+            using Mutex mutexExisting = MutexAcl.OpenExisting(name, MutexRights.FullControl);
+
+            VerifyHandles(mutexNew, mutexExisting);
+            MutexSecurity actualSecurity = mutexExisting.GetAccessControl();
+            VerifyMutexSecurity(expectedSecurity, actualSecurity);
+        }
+
+        [Fact]
+        public void Mutex_TryOpenExisting()
+        {
+            string name = GetRandomName();
+            MutexSecurity expectedSecurity = GetMutexSecurity(WellKnownSidType.BuiltinUsersSid, MutexRights.FullControl, AccessControlType.Allow);
+            using Mutex mutexNew = CreateAndVerifyMutex(initiallyOwned: true, name, expectedSecurity, expectedCreatedNew: true);
+
+            Assert.True(MutexAcl.TryOpenExisting(name, MutexRights.FullControl, out Mutex mutexExisting));
+            Assert.NotNull(mutexExisting);
+
+            VerifyHandles(mutexNew, mutexExisting);
+            MutexSecurity actualSecurity = mutexExisting.GetAccessControl();
+            VerifyMutexSecurity(expectedSecurity, actualSecurity);
 
+            mutexExisting.Dispose();
+        }
+
+        [Fact]
+        public void Mutex_OpenExisting_NameNotFound()
+        {
+            string name = "ThisShouldNotExist";
+            Assert.Throws<WaitHandleCannotBeOpenedException>(() =>
+            {
+                MutexAcl.OpenExisting(name, MutexRights.FullControl).Dispose();
+            });
+
+            Assert.False(MutexAcl.TryOpenExisting(name, MutexRights.FullControl, out _));
+        }
+
+        [Fact]
+        public void Mutex_OpenExisting_NameInvalid()
+        {
+            string name = '\0'.ToString();
+            Assert.Throws<WaitHandleCannotBeOpenedException>(() =>
+            {
+                MutexAcl.OpenExisting(name, MutexRights.FullControl).Dispose();
+            });
+
+            Assert.False(MutexAcl.TryOpenExisting(name, MutexRights.FullControl, out _));
+        }
+
+
+        [Fact]
+        public void Mutex_OpenExisting_PathNotFound()
+        {
+            string name = @"global\foo";
+            Assert.Throws<DirectoryNotFoundException>(() =>
+            {
+                MutexAcl.OpenExisting(name, MutexRights.FullControl).Dispose();
+            });
+
+            Assert.False(MutexAcl.TryOpenExisting(name, MutexRights.FullControl, out _));
+        }
+
+        [Fact]
+        public void Mutex_OpenExisting_BadPathName()
+        {
+            string name = @"\\?\Path";
+            Assert.Throws<System.IO.IOException>(() =>
+            {
+                MutexAcl.OpenExisting(name, MutexRights.FullControl).Dispose();
+            });
+            Assert.Throws<System.IO.IOException>(() =>
+            {
+                MutexAcl.TryOpenExisting(name, MutexRights.FullControl, out _);
+            });
+        }
+
+        [Fact]
+        public void Mutex_OpenExisting_NullName()
+        {
+            Assert.Throws<ArgumentNullException>(() =>
+            {
+                MutexAcl.OpenExisting(null, MutexRights.FullControl).Dispose();
+            });
+
+            Assert.Throws<ArgumentNullException>(() =>
+            {
+                MutexAcl.TryOpenExisting(null, MutexRights.FullControl, out _);
+            });
+        }
+
+        [Fact]
+        public void Mutex_OpenExisting_EmptyName()
+        {
+            Assert.Throws<ArgumentException>(() =>
+            {
+                MutexAcl.OpenExisting(string.Empty, MutexRights.FullControl).Dispose();
+            });
+
+            Assert.Throws<ArgumentException>(() =>
+            {
+                MutexAcl.TryOpenExisting(string.Empty, MutexRights.FullControl, out _);
+            });
         }
 
         private MutexSecurity GetBasicMutexSecurity()
@@ -113,6 +216,18 @@ namespace System.Threading.Tests
             return mutex;
         }
 
+        private void VerifyHandles(Mutex expected, Mutex actual)
+        {
+            Assert.NotNull(expected.SafeWaitHandle);
+            Assert.NotNull(actual.SafeWaitHandle);
+
+            Assert.False(expected.SafeWaitHandle.IsClosed);
+            Assert.False(actual.SafeWaitHandle.IsClosed);
+
+            Assert.False(expected.SafeWaitHandle.IsInvalid);
+            Assert.False(actual.SafeWaitHandle.IsInvalid);
+        }
+
         private void VerifyMutexSecurity(MutexSecurity expectedSecurity, MutexSecurity actualSecurity)
         {
             Assert.Equal(typeof(MutexRights), expectedSecurity.AccessRightType);
index c2eae57..ab3b171 100644 (file)
@@ -2,6 +2,7 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System.Collections.Generic;
+using System.IO;
 using System.Linq;
 using System.Security.AccessControl;
 using System.Security.Principal;
@@ -140,6 +141,116 @@ namespace System.Threading.Tests
 
         }
 
+        [Fact]
+        public void Semaphore_OpenExisting()
+        {
+            string name = GetRandomName();
+            SemaphoreSecurity expectedSecurity = GetSemaphoreSecurity(WellKnownSidType.BuiltinUsersSid, SemaphoreRights.FullControl, AccessControlType.Allow);
+            using Semaphore semaphoreNew = CreateAndVerifySemaphore(initialCount: 1, maximumCount: 2, name, expectedSecurity, expectedCreatedNew: true);
+
+            using Semaphore semaphoreExisting = SemaphoreAcl.OpenExisting(name, SemaphoreRights.FullControl);
+
+            VerifyHandles(semaphoreNew, semaphoreExisting);
+            SemaphoreSecurity actualSecurity = semaphoreExisting.GetAccessControl();
+            VerifySemaphoreSecurity(expectedSecurity, actualSecurity);
+        }
+
+        [Fact]
+        public void Semaphore_TryOpenExisting()
+        {
+            string name = GetRandomName();
+            SemaphoreSecurity expectedSecurity = GetSemaphoreSecurity(WellKnownSidType.BuiltinUsersSid, SemaphoreRights.FullControl, AccessControlType.Allow);
+            using Semaphore semaphoreNew = CreateAndVerifySemaphore(initialCount: 1, maximumCount: 2, name, expectedSecurity, expectedCreatedNew: true);
+
+            Assert.True(SemaphoreAcl.TryOpenExisting(name, SemaphoreRights.FullControl, out Semaphore semaphoreExisting));
+            Assert.NotNull(semaphoreExisting);
+
+            VerifyHandles(semaphoreNew, semaphoreExisting);
+            SemaphoreSecurity actualSecurity = semaphoreExisting.GetAccessControl();
+            VerifySemaphoreSecurity(expectedSecurity, actualSecurity);
+
+            semaphoreExisting.Dispose();
+        }
+
+        [Fact]
+        public void Semaphore_OpenExisting_NameNotFound()
+        {
+            string name = "ThisShouldNotExist";
+            Assert.Throws<WaitHandleCannotBeOpenedException>(() =>
+            {
+                SemaphoreAcl.OpenExisting(name, SemaphoreRights.FullControl).Dispose();
+            });
+
+            Assert.False(SemaphoreAcl.TryOpenExisting(name, SemaphoreRights.FullControl, out _));
+        }
+
+        [Fact]
+        public void Semaphore_OpenExisting_NameInvalid()
+        {
+            string name = '\0'.ToString();
+            Assert.Throws<WaitHandleCannotBeOpenedException>(() =>
+            {
+                SemaphoreAcl.OpenExisting(name, SemaphoreRights.FullControl).Dispose();
+            });
+
+            Assert.False(SemaphoreAcl.TryOpenExisting(name, SemaphoreRights.FullControl, out _));
+        }
+
+        [Fact]
+        public void Semaphore_OpenExisting_PathNotFound()
+        {
+            string name = @"global\foo";
+            Assert.Throws<IOException>(() =>
+            {
+                SemaphoreAcl.OpenExisting(name, SemaphoreRights.FullControl).Dispose();
+            });
+
+            Assert.False(SemaphoreAcl.TryOpenExisting(name, SemaphoreRights.FullControl, out _));
+        }
+
+        [Fact]
+        public void Semaphore_OpenExisting_BadPathName()
+        {
+            string name = @"\\?\Path";
+            Assert.Throws<IOException>(() =>
+            {
+                SemaphoreAcl.OpenExisting(name, SemaphoreRights.FullControl).Dispose();
+            });
+
+            Assert.Throws<IOException>(() =>
+            {
+                SemaphoreAcl.TryOpenExisting(name, SemaphoreRights.FullControl, out _);
+            });
+        }
+
+        [Fact]
+        public void Semaphore_OpenExisting_NullName()
+        {
+            Assert.Throws<ArgumentNullException>(() =>
+            {
+                SemaphoreAcl.OpenExisting(null, SemaphoreRights.FullControl).Dispose();
+            });
+
+            Assert.Throws<ArgumentNullException>(() =>
+            {
+                SemaphoreAcl.TryOpenExisting(null, SemaphoreRights.FullControl, out _);
+            });
+        }
+
+        [Fact]
+        public void Semaphore_OpenExisting_EmptyName()
+        {
+            Assert.Throws<ArgumentException>(() =>
+            {
+                SemaphoreAcl.OpenExisting(string.Empty, SemaphoreRights.FullControl).Dispose();
+            });
+
+            Assert.Throws<ArgumentException>(() =>
+            {
+                SemaphoreAcl.TryOpenExisting(string.Empty, SemaphoreRights.FullControl, out _);
+            });
+        }
+
         private SemaphoreSecurity GetBasicSemaphoreSecurity()
         {
             return GetSemaphoreSecurity(
@@ -172,6 +283,18 @@ namespace System.Threading.Tests
             return Semaphore;
         }
 
+        private void VerifyHandles(Semaphore expected, Semaphore actual)
+        {
+            Assert.NotNull(expected.SafeWaitHandle);
+            Assert.NotNull(actual.SafeWaitHandle);
+
+            Assert.False(expected.SafeWaitHandle.IsClosed);
+            Assert.False(actual.SafeWaitHandle.IsClosed);
+
+            Assert.False(expected.SafeWaitHandle.IsInvalid);
+            Assert.False(actual.SafeWaitHandle.IsInvalid);
+        }
+
         private void VerifySemaphoreSecurity(SemaphoreSecurity expectedSecurity, SemaphoreSecurity actualSecurity)
         {
             Assert.Equal(typeof(SemaphoreRights), expectedSecurity.AccessRightType);