using System; using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; using System.Threading; namespace OpenTap { internal interface IFileLock : IDisposable { bool WaitOne(); bool WaitOne(TimeSpan timeout); bool WaitOne(int ms); WaitHandle WaitHandle { get; } void Release(); } internal static class FileLock { public static IFileLock Create(string file) { if (OperatingSystem.Current == OperatingSystem.Windows) return new Win32FileLock(file); if (OperatingSystem.Current == OperatingSystem.MacOS) return new MacOSFileLock(file); return new PosixFileLock(file); } } /// /// Note that this implementation is not thread-safe, unlike the other implementations /// class MacOSFileLock : IFileLock { private readonly ManualResetEvent _waitHandle; private readonly string name; public MacOSFileLock(string file) { _waitHandle = new ManualResetEvent(false); name = file; } public void Dispose() { Release(); } public bool WaitOne() { while (true) { // Keep retrying waiting with a timeout until it succeeds if (WaitOne(1000)) return true; } } public bool WaitOne(TimeSpan timeout) { // If the fileLock is not null, we are already holding this mutex. if (fileLock != null) return true; var sw = Stopwatch.StartNew(); do { // File exists -- the named mutex is locked if (File.Exists(name)) { var remaining = timeout - sw.Elapsed; if (remaining.TotalMilliseconds > 1) Thread.Sleep(1); else Thread.Yield(); } // Otherwise, create the file, thereby claiming the mutex else { fileLock = File.Create(name, 0, FileOptions.DeleteOnClose); _waitHandle.Set(); return true; } } while (sw.Elapsed < timeout); return false; } public bool WaitOne(int ms) { return WaitOne(TimeSpan.FromMilliseconds(ms)); } public WaitHandle WaitHandle => _waitHandle; public FileStream fileLock { get; set; } public void Release() { try { fileLock.Dispose(); fileLock = null; _waitHandle.Reset(); } catch { // this is okay } } } /// Locks a file using flock on linux. This essentially works as a named mutex. class PosixFileLock : IFileLock { int fileDescriptor; string fileName; private readonly ManualResetEvent _waitHandle; public PosixFileLock(string file) { this.fileName = file; // Open 'file' in read/write + append mode. If the file does not exist it will be created with the // most permissive access settings possible fileDescriptor = PosixNative.open(file, PosixNative.O_RDONLY | PosixNative.O_APPEND | PosixNative.O_CREAT, PosixNative.ALL_READ_WRITE); if (fileDescriptor == -1) throw new IOException($"Failed create file lock on {file}"); _waitHandle = new ManualResetEvent(false); } /// /// Request an exclusive lock on the open file handle /// This call wil block until the lock is acquired /// private void Take() { PosixNative.flock(fileDescriptor, PosixNative.LOCK_EX); } public void Release() { if (fileDescriptor >= 0) { PosixNative.flock(fileDescriptor, PosixNative.LOCK_UN); _waitHandle.Reset(); } } public void Dispose() { if (fileDescriptor >= 0 && _waitHandle.WaitOne(0)) { Release(); } PosixNative.close(fileDescriptor); fileDescriptor = -1; try { File.Delete(this.fileName); } catch { // suppress } } public bool WaitOne() { Take(); _waitHandle.Set(); return true; } public bool WaitOne(TimeSpan timeout) { var sw = Stopwatch.StartNew(); do { var @lock = PosixNative.flock(fileDescriptor, PosixNative.LOCK_NB | PosixNative.LOCK_EX); if (@lock == 0) { _waitHandle.Set(); return true; } var remaining = timeout - sw.Elapsed; if (remaining.TotalMilliseconds > 1) Thread.Sleep(1); else Thread.Yield(); } while (sw.Elapsed < timeout); return false; } public bool WaitOne(int ms) => WaitOne(TimeSpan.FromMilliseconds(ms)); public WaitHandle WaitHandle => _waitHandle; } class Win32FileLock : IFileLock { private Mutex _mutex; public Win32FileLock(string name) { // Having backslashes in the mutex name seems to cause issues for some reason. Replace them with slashes. _mutex = new Mutex(false, name.Replace("\\", "/") + "_opentap_named_mutex_"); } public void Dispose() { try { if (_mutex?.WaitOne(0) == true) _mutex?.ReleaseMutex(); } catch (AbandonedMutexException) { // this is fine } _mutex?.Dispose(); _mutex = null; } public bool WaitOne() => _mutex.WaitOne(); public bool WaitOne(TimeSpan timeout) => _mutex.WaitOne(timeout); public bool WaitOne(int ms) => _mutex.WaitOne(ms); public WaitHandle WaitHandle => _mutex; public void Release() => _mutex.ReleaseMutex(); } static class PosixNative { [DllImport("libc")] public static extern int open(string pathname, int flags, int mode); [DllImport("libc")] public static extern int close(int fd); [DllImport("libc")] public static extern int flock(int fd, int operation); public const int O_CREAT = 64; //00000100; public const int O_TRUNC = 512; //00001000; public const int O_APPEND = 1024; //00002000; public const int O_RDONLY = 0; //00000000; public const int O_RDWR = 2; //00000002; /// /// Place a shared lock. More than one process may hold a shared lock for a given file at a given time. /// public const int LOCK_SH = 1; /// /// Place an exclusive lock. Only one process may hold an exclusive lock for a given file at a given time. /// public const int LOCK_EX = 2; /// /// Return an error instead of blocking when the lock is taken /// public const int LOCK_NB = 4; /// /// Release the lock /// public const int LOCK_UN = 8; public const int S_IRUSR = 256; //00000400 public const int S_IWUSR = 128; //00000200 public const int S_IRGRP = 32; //00000040 public const int S_IWGRP = 16; //00000020 public const int S_IROTH = 4; //00000004 public const int S_IWOTH = 2; //00000002 public const int ALL_READ_WRITE = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; } }