diff options
author | Ian Hays <ianha@microsoft.com> | 2017-08-08 18:53:18 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-08-08 18:53:18 +0300 |
commit | f16c8dccb7d38fbaaa69fd331d6ee339315fcaf2 (patch) | |
tree | f3d874fdc63c19796b8268d10fe9ad434450a1af /src | |
parent | 4a97f92f27d1b0bd2f2c67ffb05e50833d652ae4 (diff) | |
parent | 77883e33b94164a318eb128605340dc2e61cd358 (diff) |
Merge pull request #22976 from ianhays/fsw_filter
Add FSW file/directory event filtering to Linux
Diffstat (limited to 'src')
3 files changed, 137 insertions, 13 deletions
diff --git a/src/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.Linux.cs b/src/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.Linux.cs index 6de6aba533..04cdfb9c2a 100644 --- a/src/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.Linux.cs +++ b/src/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.Linux.cs @@ -69,7 +69,7 @@ namespace System.IO // active operations to all be outstanding at the same time. var runner = new RunningInstance( this, handle, _directory, - IncludeSubdirectories, TranslateFilters(NotifyFilter), cancellation.Token); + IncludeSubdirectories, NotifyFilter, cancellation.Token); // Now that we've created the runner, store the cancellation object and mark the instance // as running. We wait to do this so that if there was a failure, StartRaisingEvents @@ -256,7 +256,8 @@ namespace System.IO /// <summary> /// Filters to use when adding a watch on directories. /// </summary> - private readonly Interop.Sys.NotifyEvents _notifyFilters; + private readonly NotifyFilters _notifyFilters; + private readonly Interop.Sys.NotifyEvents _watchFilters; /// <summary> /// Whether to monitor subdirectories. Unlike Win32, inotify does not implicitly monitor subdirectories; /// watches must be explicitly added for those subdirectories. @@ -282,7 +283,7 @@ namespace System.IO /// <summary>Initializes the instance with all state necessary to operate a watch.</summary> internal RunningInstance( FileSystemWatcher watcher, SafeFileHandle inotifyHandle, string directoryPath, - bool includeSubdirectories, Interop.Sys.NotifyEvents notifyFilters, CancellationToken cancellationToken) + bool includeSubdirectories, NotifyFilters notifyFilters, CancellationToken cancellationToken) { Debug.Assert(watcher != null); Debug.Assert(inotifyHandle != null && !inotifyHandle.IsInvalid && !inotifyHandle.IsClosed); @@ -295,6 +296,7 @@ namespace System.IO Debug.Assert(_buffer != null && _buffer.Length > (c_INotifyEventSize + NAME_MAX + 1)); _includeSubdirectories = includeSubdirectories; _notifyFilters = notifyFilters; + _watchFilters = TranslateFilters(notifyFilters); _cancellationToken = cancellationToken; // Add a watch for this starting directory. We keep track of the watch descriptor => directory information @@ -358,7 +360,7 @@ namespace System.IO // the existing descriptor. This works even in the case of a rename. We also add the DONT_FOLLOW // and EXCL_UNLINK flags to keep parity with Windows where we don't pickup symlinks or unlinked // files (which don't exist in Windows) - int wd = Interop.Sys.INotifyAddWatch(_inotifyHandle, fullPath, (uint)(this._notifyFilters | Interop.Sys.NotifyEvents.IN_DONT_FOLLOW | Interop.Sys.NotifyEvents.IN_EXCL_UNLINK)); + int wd = Interop.Sys.INotifyAddWatch(_inotifyHandle, fullPath, (uint)(this._watchFilters | Interop.Sys.NotifyEvents.IN_DONT_FOLLOW | Interop.Sys.NotifyEvents.IN_EXCL_UNLINK)); if (wd == -1) { // If we get an error when trying to add the watch, don't let that tear down processing. Instead, @@ -637,10 +639,21 @@ namespace System.IO AddDirectoryWatch(associatedDirectoryEntry, nextEvent.name); } - const Interop.Sys.NotifyEvents switchMask = - Interop.Sys.NotifyEvents.IN_IGNORED |Interop.Sys.NotifyEvents.IN_CREATE | Interop.Sys.NotifyEvents.IN_DELETE | - Interop.Sys.NotifyEvents.IN_ACCESS | Interop.Sys.NotifyEvents.IN_MODIFY | Interop.Sys.NotifyEvents.IN_ATTRIB | - Interop.Sys.NotifyEvents.IN_MOVED_FROM | Interop.Sys.NotifyEvents.IN_MOVED_TO; + // Check if the event should have been filtered but was unable because of inotify's inability + // to filter files vs directories. + const Interop.Sys.NotifyEvents fileDirEvents = Interop.Sys.NotifyEvents.IN_CREATE | + Interop.Sys.NotifyEvents.IN_DELETE | + Interop.Sys.NotifyEvents.IN_MOVED_FROM | + Interop.Sys.NotifyEvents.IN_MOVED_TO; + if ((((uint)fileDirEvents & mask) > 0) && + (isDir && ((_notifyFilters & NotifyFilters.DirectoryName) == 0) || + (!isDir && ((_notifyFilters & NotifyFilters.FileName) == 0)))) + { + continue; + } + + const Interop.Sys.NotifyEvents switchMask = fileDirEvents | Interop.Sys.NotifyEvents.IN_IGNORED | + Interop.Sys.NotifyEvents.IN_ACCESS | Interop.Sys.NotifyEvents.IN_MODIFY | Interop.Sys.NotifyEvents.IN_ATTRIB; switch ((Interop.Sys.NotifyEvents)(mask & (uint)switchMask)) { case Interop.Sys.NotifyEvents.IN_CREATE: diff --git a/src/System.IO.FileSystem.Watcher/tests/FileSystemWatcher.Directory.NotifyFilter.cs b/src/System.IO.FileSystem.Watcher/tests/FileSystemWatcher.Directory.NotifyFilter.cs index f390e58913..fbcf20d35d 100644 --- a/src/System.IO.FileSystem.Watcher/tests/FileSystemWatcher.Directory.NotifyFilter.cs +++ b/src/System.IO.FileSystem.Watcher/tests/FileSystemWatcher.Directory.NotifyFilter.cs @@ -87,8 +87,6 @@ namespace System.IO.Tests WatcherChangeTypes expected = 0; if (filter == NotifyFilters.DirectoryName) expected |= WatcherChangeTypes.Renamed; - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && (filter == NotifyFilters.FileName)) - expected |= WatcherChangeTypes.Renamed; ExpectEvent(watcher, expected, action, cleanup, targetPath); } @@ -141,6 +139,33 @@ namespace System.IO.Tests } [Theory] + [OuterLoop] + [MemberData(nameof(FilterTypes))] + public void FileSystemWatcher_Directory_NotifyFilter_LastWriteTime_TwoFilters(NotifyFilters filter) + { + Assert.All(FilterTypes(), (filter2Arr => + { + using (var testDirectory = new TempDirectory(GetTestFilePath())) + using (var dir = new TempDirectory(Path.Combine(testDirectory.Path, "dir"))) + using (var watcher = new FileSystemWatcher(testDirectory.Path, Path.GetFileName(dir.Path))) + { + filter |= (NotifyFilters)filter2Arr[0]; + watcher.NotifyFilter = filter; + Action action = () => Directory.SetLastWriteTime(dir.Path, DateTime.Now + TimeSpan.FromSeconds(10)); + + WatcherChangeTypes expected = 0; + if ((filter & NotifyFilters.LastWrite) > 0) + expected |= WatcherChangeTypes.Changed; + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && ((filter & LinuxFiltersForAttribute) > 0)) + expected |= WatcherChangeTypes.Changed; + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && ((filter & OSXFiltersForModify) > 0)) + expected |= WatcherChangeTypes.Changed; + ExpectEvent(watcher, expected, action, expectedPath: dir.Path); + } + })); + } + + [Theory] [MemberData(nameof(FilterTypes))] [PlatformSpecific(TestPlatforms.Windows)] // Uses P/Invokes to set security info [ActiveIssue(21109, TargetFrameworkMonikers.Uap)] @@ -255,5 +280,35 @@ namespace System.IO.Tests ExpectEvent(watcher, expected, action, cleanup, new string[] { otherDir, dir.Path }); } } + + [Fact] + public void FileSystemWatcher_Directory_NotifyFilter_DirectoryNameDoesntTriggerOnFileEvent() + { + using (var testDirectory = new TempDirectory(GetTestFilePath())) + using (var dir = new TempDirectory(Path.Combine(testDirectory.Path, "dir"))) + using (var watcher = new FileSystemWatcher(testDirectory.Path, "*")) + { + watcher.NotifyFilter = NotifyFilters.FileName; + string renameDirSource = Path.Combine(testDirectory.Path, "dir2_source"); + string renameDirDest = Path.Combine(testDirectory.Path, "dir2_dest"); + string otherDir = Path.Combine(testDirectory.Path, "dir3"); + Directory.CreateDirectory(renameDirSource); + + Action action = () => + { + Directory.CreateDirectory(otherDir); + Directory.Move(renameDirSource, renameDirDest); + Directory.SetLastWriteTime(dir.Path, DateTime.Now + TimeSpan.FromSeconds(10)); + Directory.Delete(otherDir); + }; + Action cleanup = () => + { + Directory.Move(renameDirDest, renameDirSource); + }; + + WatcherChangeTypes expected = 0; + ExpectEvent(watcher, expected, action, cleanup, new string[] { otherDir, dir.Path }); + } + } } }
\ No newline at end of file diff --git a/src/System.IO.FileSystem.Watcher/tests/FileSystemWatcher.File.NotifyFilter.cs b/src/System.IO.FileSystem.Watcher/tests/FileSystemWatcher.File.NotifyFilter.cs index 30789b2546..991d42c296 100644 --- a/src/System.IO.FileSystem.Watcher/tests/FileSystemWatcher.File.NotifyFilter.cs +++ b/src/System.IO.FileSystem.Watcher/tests/FileSystemWatcher.File.NotifyFilter.cs @@ -91,9 +91,6 @@ namespace System.IO.Tests if (filter == NotifyFilters.DirectoryName) expected |= WatcherChangeTypes.Renamed; - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && (filter == NotifyFilters.FileName)) - expected |= WatcherChangeTypes.Renamed; - ExpectEvent(watcher, expected, action, cleanup, targetPath); } } @@ -168,6 +165,36 @@ namespace System.IO.Tests } [Theory] + [OuterLoop] + [MemberData(nameof(FilterTypes))] + public void FileSystemWatcher_File_NotifyFilter_Size_TwoFilters(NotifyFilters filter) + { + Assert.All(FilterTypes(), (filter2Arr => + { + using (var testDirectory = new TempDirectory(GetTestFilePath())) + using (var file = new TempFile(Path.Combine(testDirectory.Path, "file"))) + using (var watcher = new FileSystemWatcher(testDirectory.Path, Path.GetFileName(file.Path))) + { + filter |= (NotifyFilters)filter2Arr[0]; + watcher.NotifyFilter = filter; + Action action = () => File.AppendAllText(file.Path, "longText!"); + Action cleanup = () => File.AppendAllText(file.Path, "short"); + + WatcherChangeTypes expected = 0; + if (((filter & NotifyFilters.Size) > 0) || ((filter & NotifyFilters.LastWrite) > 0)) + expected |= WatcherChangeTypes.Changed; + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && ((filter & LinuxFiltersForModify) > 0)) + expected |= WatcherChangeTypes.Changed; + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && ((filter & OSXFiltersForModify) > 0)) + expected |= WatcherChangeTypes.Changed; + else if (PlatformDetection.IsWindows7 && ((filter & NotifyFilters.Attributes) > 0)) // win7 FSW Size change passes the Attribute filter + expected |= WatcherChangeTypes.Changed; + ExpectEvent(watcher, expected, action, expectedPath: file.Path); + } + })); + } + + [Theory] [MemberData(nameof(FilterTypes))] [PlatformSpecific(TestPlatforms.Windows)] // Uses P/Invokes to set security info public void FileSystemWatcher_File_NotifyFilter_Security(NotifyFilters filter) @@ -281,5 +308,34 @@ namespace System.IO.Tests ExpectEvent(watcher, expected, action, cleanup, new string[] { otherFile, file.Path }); } } + + [Fact] + public void FileSystemWatcher_File_NotifyFilter_FileNameDoesntTriggerOnDirectoryEvent() + { + using (var testDirectory = new TempDirectory(GetTestFilePath())) + using (var file = new TempFile(Path.Combine(testDirectory.Path, "file"))) + using (var sourcePath = new TempFile(Path.Combine(testDirectory.Path, "sourceFile"))) + using (var watcher = new FileSystemWatcher(testDirectory.Path, "*")) + { + watcher.NotifyFilter = NotifyFilters.DirectoryName; + string otherFile = Path.Combine(testDirectory.Path, "file2"); + string destPath = Path.Combine(testDirectory.Path, "destFile"); + + Action action = () => + { + File.Create(otherFile).Dispose(); + File.SetLastWriteTime(file.Path, DateTime.Now + TimeSpan.FromSeconds(10)); + File.Delete(otherFile); + File.Move(sourcePath.Path, destPath); + }; + Action cleanup = () => + { + File.Move(destPath, sourcePath.Path); + }; + + WatcherChangeTypes expected = 0; + ExpectEvent(watcher, expected, action, cleanup, new string[] { otherFile, file.Path }); + } + } } }
\ No newline at end of file |