/* * Greenshot - a free and open source screenshot tool * Copyright (C) 2007-2020 Thomas Braun, Jens Klingen, Robin Krom * * For more information see: http://getgreenshot.org/ * The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 1 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ using log4net; using System; using System.Security.AccessControl; using System.Security.Principal; using System.Threading; namespace Greenshot.Helpers { /// /// This protects your resources or application from running more than once /// Simplifies the usage of the Mutex class, as described here: /// https://msdn.microsoft.com/en-us/library/System.Threading.Mutex.aspx /// public class ResourceMutex : IDisposable { private static readonly ILog Log = LogManager.GetLogger(typeof(DestinationHelper)); private readonly string _mutexId; private readonly string _resourceName; private Mutex _applicationMutex; /// /// Private constructor /// /// /// private ResourceMutex(string mutexId, string resourceName = null) { _mutexId = mutexId; _resourceName = resourceName ?? "some resource"; } /// /// Test if the Mutex was created and locked. /// public bool IsLocked { get; set; } /// /// Create a ResourceMutex for the specified mutex id and resource-name /// /// ID of the mutex, preferably a Guid as string /// Name of the resource to lock, e.g your application name, usefull for logs /// true to have a global mutex see: https://msdn.microsoft.com/en-us/library/bwe34f1k.aspx public static ResourceMutex Create(string mutexId, string resourceName = null, bool global = false) { var applicationMutex = new ResourceMutex((global ? @"Global\" : @"Local\") + mutexId, resourceName); applicationMutex.Lock(); return applicationMutex; } /// /// This tries to get the Mutex, which takes care of having multiple instances running /// /// true if it worked, false if another instance is already running or something went wrong public bool Lock() { Log.DebugFormat("{0} is trying to get Mutex {1}", _resourceName, _mutexId); IsLocked = true; // check whether there's an local instance running already, but use local so this works in a multi-user environment try { // Added Mutex Security, hopefully this prevents the UnauthorizedAccessException more gracefully var sid = new SecurityIdentifier(WellKnownSidType.WorldSid, null); var mutexsecurity = new MutexSecurity(); mutexsecurity.AddAccessRule(new MutexAccessRule(sid, MutexRights.FullControl, AccessControlType.Allow)); mutexsecurity.AddAccessRule(new MutexAccessRule(sid, MutexRights.ChangePermissions, AccessControlType.Deny)); mutexsecurity.AddAccessRule(new MutexAccessRule(sid, MutexRights.Delete, AccessControlType.Deny)); // 1) Create Mutex _applicationMutex = new Mutex(true, _mutexId, out var createdNew, mutexsecurity); // 2) if the mutex wasn't created new get the right to it, this returns false if it's already locked if (!createdNew && !_applicationMutex.WaitOne(100, false)) { Log.InfoFormat("{0} is already in use, mutex {1} is NOT locked for the caller", _resourceName, _mutexId); IsLocked = false; // Clean up _applicationMutex.Close(); _applicationMutex = null; } else { Log.InfoFormat(createdNew ? "{0} has created & claimed the mutex {1}" : "{0} has claimed the mutex {1}", _resourceName, _mutexId); } } catch (AbandonedMutexException e) { // Another instance didn't cleanup correctly! // we can ignore the exception, it happend on the "waitone" but still the mutex belongs to us Log.WarnFormat("{0} didn't cleanup correctly, but we got the mutex {1}.", _resourceName, _mutexId); Log.Warn(e); } catch (UnauthorizedAccessException e) { Log.ErrorFormat("{0} is most likely already running for a different user in the same session, can't create/get mutex {1} due to error.", _resourceName, _mutexId); Log.Error(e); IsLocked = false; } catch (Exception ex) { Log.ErrorFormat("Problem obtaining the Mutex {1} for {0}, assuming it was already taken!", _resourceName, _mutexId); Log.Error(ex); IsLocked = false; } return IsLocked; } // To detect redundant Dispose calls private bool _disposedValue; /// /// The real disposing code /// /// true if dispose is called, false when the finalizer is called protected virtual void Dispose(bool disposing) { if (!_disposedValue) { if (_applicationMutex != null) { try { _applicationMutex.ReleaseMutex(); _applicationMutex = null; Log.InfoFormat("Released Mutex {0} for {1}", _mutexId, _resourceName); } catch (Exception ex) { Log.ErrorFormat("Error releasing Mutex {0} for {1}", _mutexId, _resourceName); Log.Error(ex); } } _disposedValue = true; } } /// /// Make sure the ApplicationMutex is disposed when the finalizer is called /// ~ResourceMutex() { // Do not change this code. Put cleanup code in Dispose(bool disposing) above. Dispose(false); } /// /// The dispose interface, which calls Dispose(true) to signal that dispose is called. /// public void Dispose() { // Do not change this code. Put cleanup code in Dispose(bool disposing) above. Dispose(true); GC.SuppressFinalize(this); } } }