//****************************************************************************************************** // InterprocessLock.cs - Gbtc // // Copyright © 2012, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the Eclipse Public License -v 1.0 (the "License"); you may // not use this file except in compliance with the License. You may obtain a copy of the License at: // // http://www.opensource.org/licenses/eclipse-1.0.php // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 03/21/2011 - J. Ritchie Carroll // Generated original version of source code. // 06/30/2011 - Stephen Wills // Applying changes from Jian (Ryan) Zuo: updated to allow unauthorized users to attempt to grant // themselves lower than full access to existing mutexes and semaphores. // 08/12/2011 - J. Ritchie Carroll // Modified creation methods such that locking natives are created in a synchronized fashion. // 09/21/2011 - J. Ritchie Carroll // Added Mono implementation exception regions. // 12/14/2012 - Starlynn Danyelle Gilliam // Modified Header. // //****************************************************************************************************** using System; using System.Runtime.CompilerServices; using System.Security.AccessControl; using System.Security.Principal; using System.Threading; using GSF.Security.Cryptography; namespace GSF.Threading { /// /// Defines helper methods related to interprocess locking. /// public static class InterprocessLock { private const int MutexHash = 0; private const int SemaphoreHash = 1; /// /// Gets a uniquely named interprocess associated with the specified that identifies a source object /// needing concurrency locking. /// /// Identifiying name of source object needing concurrency locking (e.g., a path and file name). /// A uniquely named interprocess specific to ; is created if it does not exist. /// /// /// This function uses a hash of the when creating the , not the actual - this way /// restrictions on the do not need to be a user concern. All processes needing an interprocess need to /// use this same function to ensure access to the same . /// /// /// The can be a string of any length (must not be empty, null or white space) and is not case-sensitive. All hashes of the /// used to create the global are first converted to lower case. /// /// /// The created is "Global" meaning that it will be accessible to all active application sessions including terminal service /// sessions. This is accomplished internally by prefixing the name with "Global\"; it is not necessary for the user to be /// concerned with the length or contents of the in this method as long as the same is used for /// each application. Do not use this helper function if you need to create a specifically named or non-global , such as when /// you need to interact with another application using a that does not use this function. /// /// /// Argument cannot be empty, null or white space. [MethodImpl(MethodImplOptions.Synchronized)] public static Mutex GetNamedMutex(string name) { if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException("name", "Argument cannot be empty, null or white space."); Mutex namedMutex = null; bool doesNotExist = false; bool unauthorized = false; // Create a mutex name that is specific to an object (e.g., a path and file name). // Prefix mutext name with "Global\" such that mutex will apply to all active // application sessions in case terminal services is running. string mutexName = "Global\\" + Cipher.GetPasswordHash(name.ToLower(), MutexHash).Replace('\\', '-'); // Attempt to open the named mutex try { namedMutex = Mutex.OpenExisting(mutexName); } catch (WaitHandleCannotBeOpenedException) { doesNotExist = true; } catch (UnauthorizedAccessException) { unauthorized = true; } // Mono Mutex implementations do not include ability to change access rules #if MONO // If mutex does not exist we create it if (doesNotExist || unauthorized) { try { bool mutexWasCreated; namedMutex = new Mutex(false, mutexName, out mutexWasCreated); if (!mutexWasCreated) throw new InvalidOperationException("Failed to create mutex."); } catch (UnauthorizedAccessException ex) { throw new InvalidOperationException("Failed to create mutex: " + ex.Message); } } #else // If mutex does not exist we create it if (doesNotExist) { try { MutexSecurity security = new MutexSecurity(); bool mutexWasCreated; security.AddAccessRule(new MutexAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), MutexRights.FullControl, AccessControlType.Allow)); namedMutex = new Mutex(false, mutexName, out mutexWasCreated, security); if (!mutexWasCreated) throw new InvalidOperationException("Failed to create mutex."); } catch (UnauthorizedAccessException) { MutexSecurity security = new MutexSecurity(); bool mutexWasCreated; security.AddAccessRule(new MutexAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), MutexRights.Synchronize | MutexRights.Modify, AccessControlType.Allow)); namedMutex = new Mutex(false, mutexName, out mutexWasCreated, security); if (!mutexWasCreated) throw new InvalidOperationException("Failed to create mutex."); } } if (unauthorized) { namedMutex = Mutex.OpenExisting(mutexName, MutexRights.ReadPermissions | MutexRights.ChangePermissions); // Get the current ACL. This requires MutexRights.ReadPermission MutexSecurity security = namedMutex.GetAccessControl(); MutexAccessRule rule = new MutexAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), MutexRights.FullControl, AccessControlType.Allow); security.RemoveAccessRule(rule); // Now grant specific user rights for less than full access rule = new MutexAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), MutexRights.Synchronize | MutexRights.Modify, AccessControlType.Allow); security.AddAccessRule(rule); // Update the ACL. This requires MutexRighs.ChangePermission. namedMutex.SetAccessControl(security); namedMutex = Mutex.OpenExisting(mutexName); } #endif return namedMutex; } /// /// Gets a uniquely named interprocess associated with the specified that identifies a source object /// needing concurrency locking. /// /// Identifiying name of source object needing concurrency locking (e.g., a path and file name). /// The maximum number of requests for the semaphore that can be granted concurrently. /// The initial number of requests for the semaphore that can be granted concurrently, or -1 to default to . /// A uniquely named interprocess specific to ; is created if it does not exist. /// /// /// This function uses a hash of the when creating the , not the actual - this way /// restrictions on the do not need to be a user concern. All processes needing an interprocess need to /// use this same function to ensure access to the same . /// /// /// The can be a string of any length (must not be empty, null or white space) and is not case-sensitive. All hashes of the /// used to create the global are first converted to lower case. /// /// /// It is not necessary for the user to be concerned with the length or contents of the in this method as long as the same /// is used for each application. Do not use this helper function if you need to create a specifically named , /// such as when you need to interact with another application using a that does not use this function. /// /// /// Argument cannot be empty, null or white space. [MethodImpl(MethodImplOptions.Synchronized)] public static Semaphore GetNamedSemaphore(string name, int maximumCount = 10, int initialCount = -1) { if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException("name", "Argument cannot be empty, null or white space."); Semaphore namedSemaphore = null; bool doesNotExist = false; bool unauthorized = false; if (initialCount < 0) initialCount = maximumCount; // Create a semaphore name that is specific to an object (e.g., a path and file name). string semaphoreName = Cipher.GetPasswordHash(name.ToLower(), SemaphoreHash).Replace('\\', '-'); // Attempt to open the named semaphore try { namedSemaphore = Semaphore.OpenExisting(semaphoreName); } catch (WaitHandleCannotBeOpenedException) { doesNotExist = true; } catch (UnauthorizedAccessException) { unauthorized = true; } // Mono Semaphore implementations do not include ability to change access rules #if MONO // If semaphore does not exist we create it if (doesNotExist || unauthorized) { try { bool semaphoreWasCreated; namedSemaphore = new Semaphore(initialCount, maximumCount, semaphoreName, out semaphoreWasCreated); if (!semaphoreWasCreated) throw new InvalidOperationException("Failed to create semaphore."); } catch (UnauthorizedAccessException ex) { throw new InvalidOperationException("Failed to create semaphore: " + ex.Message); } } #else // If semaphore does not exist we create it if (doesNotExist) { try { SemaphoreSecurity security = new SemaphoreSecurity(); bool semaphoreWasCreated; security.AddAccessRule(new SemaphoreAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), SemaphoreRights.FullControl, AccessControlType.Allow)); namedSemaphore = new Semaphore(initialCount, maximumCount, semaphoreName, out semaphoreWasCreated, security); if (!semaphoreWasCreated) throw new InvalidOperationException("Failed to create semaphore."); } catch (UnauthorizedAccessException) { SemaphoreSecurity security = new SemaphoreSecurity(); bool semaphoreWasCreated; security.AddAccessRule(new SemaphoreAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), SemaphoreRights.Synchronize | SemaphoreRights.Modify, AccessControlType.Allow)); namedSemaphore = new Semaphore(initialCount, maximumCount, semaphoreName, out semaphoreWasCreated, security); if (!semaphoreWasCreated) throw new InvalidOperationException("Failed to create semaphore."); } } else if (unauthorized) { namedSemaphore = Semaphore.OpenExisting(semaphoreName, SemaphoreRights.ReadPermissions | SemaphoreRights.ChangePermissions); // Get the current ACL. This requires MutexRights.ReadPermission SemaphoreSecurity security = new SemaphoreSecurity(); SemaphoreAccessRule rule = new SemaphoreAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), SemaphoreRights.FullControl, AccessControlType.Allow); security.RemoveAccessRule(rule); // Now grant specific user rights for less than full access rule = new SemaphoreAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), SemaphoreRights.Synchronize | SemaphoreRights.Modify, AccessControlType.Allow); security.AddAccessRule(rule); // Update the ACL. This requires MutexRighs.ChangePermission. namedSemaphore.SetAccessControl(security); namedSemaphore = Semaphore.OpenExisting(semaphoreName); } #endif return namedSemaphore; } } }