From 0c472a6ef06f130b7a5566c856b5617b3f435557 Mon Sep 17 00:00:00 2001 From: brianwilkerson Date: Thu, 18 Sep 2025 21:26:08 +0000 Subject: [PATCH 1/2] Don't follow symlinks when creating a DirectoryWatcher. --- pkgs/watcher/CHANGELOG.md | 9 +++++++++ pkgs/watcher/lib/src/directory_watcher/linux.dart | 4 ++-- .../watcher/lib/src/directory_watcher/mac_os.dart | 4 ++-- .../lib/src/directory_watcher/polling.dart | 2 +- .../lib/src/directory_watcher/windows.dart | 4 ++-- pkgs/watcher/pubspec.yaml | 2 +- pkgs/watcher/test/directory_watcher/shared.dart | 15 +++++++++++++++ pkgs/watcher/test/utils.dart | 13 +++++++++++++ 8 files changed, 45 insertions(+), 8 deletions(-) diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index 30a19756e3..d4c7bbce3b 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,3 +1,12 @@ +## 2.0.0 + +- Changes the behavior of the `DirectoryWatcher` so that it no longer follows + symlinks. On Linux, but not on MacOS or Windows, it used to watch directories + to which there was a symlink within the directory being watched. It no longer + does that. Code that depends on that behavior will need to be updated so that + it independently traverses the root directory, locating and resolving + symlinks, and watching any directories being linked to. + ## 1.1.3 - Improve handling of diff --git a/pkgs/watcher/lib/src/directory_watcher/linux.dart b/pkgs/watcher/lib/src/directory_watcher/linux.dart index cb1d077816..2a6e1ebcde 100644 --- a/pkgs/watcher/lib/src/directory_watcher/linux.dart +++ b/pkgs/watcher/lib/src/directory_watcher/linux.dart @@ -92,7 +92,7 @@ class _LinuxDirectoryWatcher }); _listen( - Directory(path).list(recursive: true), + Directory(path).list(recursive: true, followLinks: false), (FileSystemEntity entity) { if (entity is Directory) { _watchSubdir(entity.path); @@ -228,7 +228,7 @@ class _LinuxDirectoryWatcher /// Emits [ChangeType.ADD] events for the recursive contents of [path]. void _addSubdir(String path) { - _listen(Directory(path).list(recursive: true), (FileSystemEntity entity) { + _listen(Directory(path).list(recursive: true, followLinks: false), (FileSystemEntity entity) { if (entity is Directory) { _watchSubdir(entity.path); } else { diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart index b461383474..b57c6e1068 100644 --- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart +++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart @@ -148,7 +148,7 @@ class _MacOSDirectoryWatcher if (_files.containsDir(path)) continue; - var stream = Directory(path).list(recursive: true); + var stream = Directory(path).list(recursive: true, followLinks: false); var subscription = stream.listen((entity) { if (entity is Directory) return; if (_files.contains(path)) return; @@ -373,7 +373,7 @@ class _MacOSDirectoryWatcher _files.clear(); var completer = Completer(); - var stream = Directory(path).list(recursive: true); + var stream = Directory(path).list(recursive: true, followLinks: false); _initialListSubscription = stream.listen((entity) { if (entity is! Directory) _files.add(entity.path); }, onError: _emitError, onDone: completer.complete, cancelOnError: true); diff --git a/pkgs/watcher/lib/src/directory_watcher/polling.dart b/pkgs/watcher/lib/src/directory_watcher/polling.dart index 207679b1a1..67b5ee6a02 100644 --- a/pkgs/watcher/lib/src/directory_watcher/polling.dart +++ b/pkgs/watcher/lib/src/directory_watcher/polling.dart @@ -112,7 +112,7 @@ class _PollingDirectoryWatcher _filesToProcess.add(null); } - var stream = Directory(path).list(recursive: true); + var stream = Directory(path).list(recursive: true, followLinks: false); _listSubscription = stream.listen((entity) { assert(!_events.isClosed); diff --git a/pkgs/watcher/lib/src/directory_watcher/windows.dart b/pkgs/watcher/lib/src/directory_watcher/windows.dart index 8f212684c0..0f503205ee 100644 --- a/pkgs/watcher/lib/src/directory_watcher/windows.dart +++ b/pkgs/watcher/lib/src/directory_watcher/windows.dart @@ -185,7 +185,7 @@ class _WindowsDirectoryWatcher if (_files.containsDir(path)) continue; - var stream = Directory(path).list(recursive: true); + var stream = Directory(path).list(recursive: true, followLinks: false); var subscription = stream.listen((entity) { if (entity is Directory) return; if (_files.contains(entity.path)) return; @@ -435,7 +435,7 @@ class _WindowsDirectoryWatcher _files.clear(); var completer = Completer(); - var stream = Directory(path).list(recursive: true); + var stream = Directory(path).list(recursive: true, followLinks: false); void handleEntity(FileSystemEntity entity) { if (entity is! Directory) _files.add(entity.path); } diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index 16af27bc26..9ab46d275b 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,5 +1,5 @@ name: watcher -version: 1.1.3 +version: 2.0.0 description: >- A file system watcher. It monitors changes to contents of directories and sends notifications when files have been added, removed, or modified. diff --git a/pkgs/watcher/test/directory_watcher/shared.dart b/pkgs/watcher/test/directory_watcher/shared.dart index 1ebc78d4b9..aa5f6040e3 100644 --- a/pkgs/watcher/test/directory_watcher/shared.dart +++ b/pkgs/watcher/test/directory_watcher/shared.dart @@ -341,4 +341,19 @@ void sharedTests() { await inAnyOrder(events); }); }); + group('symlinks', () { + test('are not watched when not enabled', () async { + writeFile('dir/sub/a.txt', contents: 'a'); + writeFile('sibling/sub/b.txt', contents: '1'); + writeSymlink('dir/linked', target: 'sibling'); + await startWatcher(path: 'dir', followLinks: false); + + writeFile('sibling/sub/b.txt', contents: '2'); + // Ensure that any events for the first modification arrive before the + // events for the second modification. + await pumpEventQueue(); + writeFile('dir/sub/a.txt', contents: 'a'); + await expectModifyEvent('dir/sub/a.txt'); + }); + }); } diff --git a/pkgs/watcher/test/utils.dart b/pkgs/watcher/test/utils.dart index 7867b9fc29..88062ca4c4 100644 --- a/pkgs/watcher/test/utils.dart +++ b/pkgs/watcher/test/utils.dart @@ -267,6 +267,19 @@ void renameDir(String from, String to) { void deleteDir(String path) { Directory(p.join(d.sandbox, path)).deleteSync(recursive: true); } + +/// Schedules writing a symlink in the sandbox at [path] that links to [target]. +void writeSymlink(String path, {required String target}) { + var fullPath = p.join(d.sandbox, path); + + // Create any needed subdirectories. + var dir = Directory(p.dirname(fullPath)); + if (!dir.existsSync()) { + dir.createSync(recursive: true); + } + + Link(fullPath).createSync(target); +} /// Runs [callback] with every permutation of non-negative numbers for each /// argument less than [limit]. From 0c7bfb0deead2f4a79c951398fb59174a66579c6 Mon Sep 17 00:00:00 2001 From: brianwilkerson Date: Thu, 18 Sep 2025 21:36:38 +0000 Subject: [PATCH 2/2] Fixed test code --- pkgs/watcher/test/directory_watcher/shared.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/watcher/test/directory_watcher/shared.dart b/pkgs/watcher/test/directory_watcher/shared.dart index aa5f6040e3..b357221215 100644 --- a/pkgs/watcher/test/directory_watcher/shared.dart +++ b/pkgs/watcher/test/directory_watcher/shared.dart @@ -346,7 +346,7 @@ void sharedTests() { writeFile('dir/sub/a.txt', contents: 'a'); writeFile('sibling/sub/b.txt', contents: '1'); writeSymlink('dir/linked', target: 'sibling'); - await startWatcher(path: 'dir', followLinks: false); + await startWatcher(path: 'dir'); writeFile('sibling/sub/b.txt', contents: '2'); // Ensure that any events for the first modification arrive before the