From e15d0c5e6d87ebe56da4c076042f4a189b881784 Mon Sep 17 00:00:00 2001 From: Carlos Sanchez <1175054+carlossanlop@users.noreply.github.com> Date: Fri, 16 Oct 2020 17:12:30 -0700 Subject: [PATCH] Port ACL OpenExisting overloads for EventWaitHandle/Mutex/Semaphore (#43134) * 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 --- .../src/System/Threading/OpenExistingResult.cs | 17 +++ .../src/System.Private.CoreLib.Shared.projitems | 3 + .../System/Threading/EventWaitHandle.Windows.cs | 2 +- .../src/System/Threading/Semaphore.Windows.cs | 2 +- .../src/System/Threading/WaitHandle.cs | 8 -- .../ref/System.Threading.AccessControl.cs | 6 + .../src/Resources/Strings.resx | 17 ++- .../src/System.Threading.AccessControl.csproj | 2 + .../src/System/Threading/EventWaitHandleAcl.cs | 113 ++++++++++++++-- .../System/Threading/EventWaitHandleAcl.net46.cs | 10 ++ .../src/System/Threading/MutexAcl.cs | 124 +++++++++++++++--- .../src/System/Threading/MutexAcl.net46.cs | 10 ++ .../src/System/Threading/SemaphoreAcl.cs | 111 ++++++++++++++-- .../src/System/Threading/SemaphoreAcl.net46.cs | 10 ++ .../tests/EventWaitHandleAclTests.cs | 123 ++++++++++++++++++ .../tests/MutexAclTests.cs | 143 +++++++++++++++++++-- .../tests/SemaphoreAclTests.cs | 123 ++++++++++++++++++ 17 files changed, 744 insertions(+), 80 deletions(-) create mode 100644 src/libraries/Common/src/System/Threading/OpenExistingResult.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 index 0000000..16d9aba --- /dev/null +++ b/src/libraries/Common/src/System/Threading/OpenExistingResult.cs @@ -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 +{ + /// + /// 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. + /// + internal enum OpenExistingResult + { + Success, + NameNotFound, + PathNotFound, + NameInvalid + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 4ac94b0..8c2111e 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -1134,6 +1134,9 @@ Common\System\Text\ValueStringBuilder.cs + + Common\System\Threading\OpenExistingResult.cs + Common\System\Threading\Tasks\TaskToApm.cs diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/EventWaitHandle.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/EventWaitHandle.Windows.cs index 4c142bc..73256bc 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/EventWaitHandle.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/EventWaitHandle.Windows.cs @@ -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); diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Semaphore.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Semaphore.Windows.cs index d96882e..9cef42f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Semaphore.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Semaphore.Windows.cs @@ -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(); diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitHandle.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitHandle.cs index ee84050..9fcbf20 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitHandle.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitHandle.cs @@ -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). diff --git a/src/libraries/System.Threading.AccessControl/ref/System.Threading.AccessControl.cs b/src/libraries/System.Threading.AccessControl/ref/System.Threading.AccessControl.cs index b42eb33..b897c38 100644 --- a/src/libraries/System.Threading.AccessControl/ref/System.Threading.AccessControl.cs +++ b/src/libraries/System.Threading.AccessControl/ref/System.Threading.AccessControl.cs @@ -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 { diff --git a/src/libraries/System.Threading.AccessControl/src/Resources/Strings.resx b/src/libraries/System.Threading.AccessControl/src/Resources/Strings.resx index f676b7b..3503004 100644 --- a/src/libraries/System.Threading.AccessControl/src/Resources/Strings.resx +++ b/src/libraries/System.Threading.AccessControl/src/Resources/Strings.resx @@ -117,24 +117,27 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + Enum value was out of legal range. + Non-negative number required. Positive number required. - - The initial count for the semaphore must be greater than or equal to zero and less than the maximum count. - Argument cannot be null or empty. + + Empty name is not legal. + + + The initial count for the semaphore must be greater than or equal to zero and less than the maximum count. + The length of the name exceeds the maximum limit. - - Enum value was out of legal range. - Cannot create '{0}' because a file or directory with the same name already exists. @@ -180,4 +183,4 @@ A WaitHandle with system-wide name '{0}' cannot be created. A WaitHandle of a different type might have the same name. - \ No newline at end of file + diff --git a/src/libraries/System.Threading.AccessControl/src/System.Threading.AccessControl.csproj b/src/libraries/System.Threading.AccessControl/src/System.Threading.AccessControl.csproj index df22a86..5a898d9 100644 --- a/src/libraries/System.Threading.AccessControl/src/System.Threading.AccessControl.csproj +++ b/src/libraries/System.Threading.AccessControl/src/System.Threading.AccessControl.csproj @@ -34,6 +34,8 @@ Link="Interop\Windows\Kernel32\Interop.Semaphore.cs" /> + diff --git a/src/libraries/System.Threading.AccessControl/src/System/Threading/EventWaitHandleAcl.cs b/src/libraries/System.Threading.AccessControl/src/System/Threading/EventWaitHandleAcl.cs index 036a7eb..e344db4 100644 --- a/src/libraries/System.Threading.AccessControl/src/System/Threading/EventWaitHandleAcl.cs +++ b/src/libraries/System.Threading.AccessControl/src/System/Threading/EventWaitHandleAcl.cs @@ -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) + /// + /// Opens a specified named event wait handle, if it already exists, applying the desired access rights. + /// + /// 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. + /// The desired access rights to apply to the returned event wait handle. + /// An existing named event wait handle. + /// is . + /// is an empty string. + /// The named event wait handle does not exist or is invalid. + /// The path was not found. + /// A Win32 error occurred. + /// The named event wait handle exists, but the user does not have the security access required to use it. + 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; + } + } + + /// + /// 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. + /// + /// 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. + /// The desired access rights to apply to the returned event wait handle. + /// When this method returns , contains an object that represents the named event wait handle if the call succeeded, or otherwise. This parameter is treated as uninitialized. + /// if the named event wait handle was opened successfully; otherwise, . + /// is + /// is an empty string. + /// A Win32 error occurred. + /// The named event wait handle exists, but the user does not have the security access required to use it. + 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; } } } diff --git a/src/libraries/System.Threading.AccessControl/src/System/Threading/EventWaitHandleAcl.net46.cs b/src/libraries/System.Threading.AccessControl/src/System/Threading/EventWaitHandleAcl.net46.cs index 0cdc012..a1bb177 100644 --- a/src/libraries/System.Threading.AccessControl/src/System/Threading/EventWaitHandleAcl.net46.cs +++ b/src/libraries/System.Threading.AccessControl/src/System/Threading/EventWaitHandleAcl.net46.cs @@ -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); + } } } diff --git a/src/libraries/System.Threading.AccessControl/src/System/Threading/MutexAcl.cs b/src/libraries/System.Threading.AccessControl/src/System/Threading/MutexAcl.cs index f289242..b19a310 100644 --- a/src/libraries/System.Threading.AccessControl/src/System/Threading/MutexAcl.cs +++ b/src/libraries/System.Threading.AccessControl/src/System/Threading/MutexAcl.cs @@ -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) + /// + /// Opens a specified named mutex, if it already exists, applying the desired access rights. + /// + /// 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. + /// The desired access rights to apply to the returned mutex. + /// An existing named mutex. + /// is . + /// is an empty string. + /// The named mutex does not exist or is invalid. + /// The path was not found. + /// A Win32 error occurred. + /// The named mutex exists, but the user does not have the security access required to use it. + 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; + } + } + + /// + /// 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. + /// + /// 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. + /// The desired access rights to apply to the returned mutex. + /// When this method returns , contains an object that represents the named mutex if the call succeeded, or otherwise. This parameter is treated as uninitialized. + /// if the named mutex was opened successfully; otherwise, . + /// is + /// is an empty string. + /// A Win32 error occurred. + /// The named mutex exists, but the user does not have the security access required to use it. + 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; } } } diff --git a/src/libraries/System.Threading.AccessControl/src/System/Threading/MutexAcl.net46.cs b/src/libraries/System.Threading.AccessControl/src/System/Threading/MutexAcl.net46.cs index 2c285a6..0cc8e1f 100644 --- a/src/libraries/System.Threading.AccessControl/src/System/Threading/MutexAcl.net46.cs +++ b/src/libraries/System.Threading.AccessControl/src/System/Threading/MutexAcl.net46.cs @@ -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); + } } } diff --git a/src/libraries/System.Threading.AccessControl/src/System/Threading/SemaphoreAcl.cs b/src/libraries/System.Threading.AccessControl/src/System/Threading/SemaphoreAcl.cs index 35d6529..eee9206 100644 --- a/src/libraries/System.Threading.AccessControl/src/System/Threading/SemaphoreAcl.cs +++ b/src/libraries/System.Threading.AccessControl/src/System/Threading/SemaphoreAcl.cs @@ -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) + /// + /// Opens a specified named semaphore, if it already exists, applying the desired access rights. + /// + /// 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. + /// The desired access rights to apply to the returned semaphore. + /// An existing named semaphore. + /// is . + /// is an empty string. + /// The named semaphore does not exist or is invalid. + /// The path was not found. + /// -or- + /// A Win32 error occurred. + /// The named semaphore exists, but the user does not have the security access required to use it. + 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; + } + } + + /// + /// 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. + /// + /// 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. + /// The desired access rights to apply to the returned semaphore. + /// When this method returns , contains an object that represents the named semaphore if the call succeeded, or otherwise. This parameter is treated as uninitialized. + /// if the named semaphore was opened successfully; otherwise, . + /// is + /// is an empty string. + /// A Win32 error occurred. + /// The named semaphore exists, but the user does not have the security access required to use it. + 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; } } } diff --git a/src/libraries/System.Threading.AccessControl/src/System/Threading/SemaphoreAcl.net46.cs b/src/libraries/System.Threading.AccessControl/src/System/Threading/SemaphoreAcl.net46.cs index 98b88ac..b4f6590 100644 --- a/src/libraries/System.Threading.AccessControl/src/System/Threading/SemaphoreAcl.net46.cs +++ b/src/libraries/System.Threading.AccessControl/src/System/Threading/SemaphoreAcl.net46.cs @@ -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); + } } } diff --git a/src/libraries/System.Threading.AccessControl/tests/EventWaitHandleAclTests.cs b/src/libraries/System.Threading.AccessControl/tests/EventWaitHandleAclTests.cs index c6aca23..3e9324e 100644 --- a/src/libraries/System.Threading.AccessControl/tests/EventWaitHandleAclTests.cs +++ b/src/libraries/System.Threading.AccessControl/tests/EventWaitHandleAclTests.cs @@ -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(() => + { + 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(() => + { + 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(() => + { + 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(() => + { + EventWaitHandleAcl.OpenExisting(name, EventWaitHandleRights.FullControl).Dispose(); + }); + + Assert.Throws(() => + { + EventWaitHandleAcl.TryOpenExisting(name, EventWaitHandleRights.FullControl, out _); + }); + } + + [Fact] + public void EventWaitHandle_OpenExisting_NullName() + { + Assert.Throws(() => + { + EventWaitHandleAcl.OpenExisting(null, EventWaitHandleRights.FullControl).Dispose(); + }); + + Assert.Throws(() => + { + EventWaitHandleAcl.TryOpenExisting(null, EventWaitHandleRights.FullControl, out _); + }); + } + + [Fact] + public void EventWaitHandle_OpenExisting_EmptyName() + { + Assert.Throws(() => + { + EventWaitHandleAcl.OpenExisting(string.Empty, EventWaitHandleRights.FullControl).Dispose(); + }); + + Assert.Throws(() => + { + 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); diff --git a/src/libraries/System.Threading.AccessControl/tests/MutexAclTests.cs b/src/libraries/System.Threading.AccessControl/tests/MutexAclTests.cs index 218c117..3f90570 100644 --- a/src/libraries/System.Threading.AccessControl/tests/MutexAclTests.cs +++ b/src/libraries/System.Threading.AccessControl/tests/MutexAclTests.cs @@ -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 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(() => + { + 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(() => + { + 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(() => + { + 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(() => + { + MutexAcl.OpenExisting(name, MutexRights.FullControl).Dispose(); + }); + Assert.Throws(() => + { + MutexAcl.TryOpenExisting(name, MutexRights.FullControl, out _); + }); + } + + [Fact] + public void Mutex_OpenExisting_NullName() + { + Assert.Throws(() => + { + MutexAcl.OpenExisting(null, MutexRights.FullControl).Dispose(); + }); + + Assert.Throws(() => + { + MutexAcl.TryOpenExisting(null, MutexRights.FullControl, out _); + }); + } + + [Fact] + public void Mutex_OpenExisting_EmptyName() + { + Assert.Throws(() => + { + MutexAcl.OpenExisting(string.Empty, MutexRights.FullControl).Dispose(); + }); + + Assert.Throws(() => + { + 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); diff --git a/src/libraries/System.Threading.AccessControl/tests/SemaphoreAclTests.cs b/src/libraries/System.Threading.AccessControl/tests/SemaphoreAclTests.cs index c2eae57..ab3b171 100644 --- a/src/libraries/System.Threading.AccessControl/tests/SemaphoreAclTests.cs +++ b/src/libraries/System.Threading.AccessControl/tests/SemaphoreAclTests.cs @@ -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(() => + { + 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(() => + { + 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(() => + { + 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(() => + { + SemaphoreAcl.OpenExisting(name, SemaphoreRights.FullControl).Dispose(); + }); + + Assert.Throws(() => + { + SemaphoreAcl.TryOpenExisting(name, SemaphoreRights.FullControl, out _); + }); + } + + [Fact] + public void Semaphore_OpenExisting_NullName() + { + Assert.Throws(() => + { + SemaphoreAcl.OpenExisting(null, SemaphoreRights.FullControl).Dispose(); + }); + + Assert.Throws(() => + { + SemaphoreAcl.TryOpenExisting(null, SemaphoreRights.FullControl, out _); + }); + } + + [Fact] + public void Semaphore_OpenExisting_EmptyName() + { + Assert.Throws(() => + { + SemaphoreAcl.OpenExisting(string.Empty, SemaphoreRights.FullControl).Dispose(); + }); + + Assert.Throws(() => + { + 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); -- 2.7.4