Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 57b5a87

Browse files
committed
fix(tail): watch both file and parent on Linux to fix follow-name tests
Previously, Linux inotify only watched the parent directory while kqueue (macOS/BSD) watched both file and parent. This caused event path mismatches on Linux when files were renamed: 1. File /tmp/dir/file.txt is tracked in HashMap 2. File gets renamed to /tmp/dir/backup 3. Event fires with path=/tmp/dir/backup 4. HashMap lookup fails -> event ignored -> tests fail Fixes 5 failing tests on Linux CI: - test_follow_name_move1 - test_follow_name_move_create1 - test_follow_name_move2 - test_follow_name_truncate1 - test_follow_name_move_retry2 By unifying the implementation to watch both file and parent on all platforms, we ensure: - Modification events captured directly from file watch - Rename/delete events captured from parent watch - Consistent path tracking across all platforms - No event path mismatches
1 parent 332a3b8 commit 57b5a87

File tree

1 file changed

+25
-33
lines changed

1 file changed

+25
-33
lines changed

src/uu/tail/src/follow/watch.rs

Lines changed: 25 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -71,49 +71,41 @@ impl WatcherRx {
7171
> be unexpected. See discussions in [#165] and [#166]. If less surprising behavior is wanted
7272
> one may non-recursively watch the _parent_ directory as well and manage related events.
7373
74-
On Linux (inotify): Watch parent only - events report child file paths
75-
On macOS/BSD (kqueue): Watch BOTH file and parent - parent alone doesn't report child events
76-
77-
This fix applies to:
78-
- Linux: inotify backend (parent only)
79-
- macOS/FreeBSD/OpenBSD/NetBSD/DragonFly: kqueue backend (both)
80-
81-
Without this, kqueue doesn't properly detect when files are deleted/renamed,
82-
causing --follow=name to exit prematurely instead of waiting for files to reappear.
74+
Implementation: Watch BOTH file and parent on all platforms
75+
- File watch: Captures modification events directly
76+
- Parent watch: Captures rename/delete events reliably
77+
78+
This dual-watch approach is necessary because:
79+
- On kqueue (macOS/BSD): Parent watch alone doesn't report child file modifications
80+
- On inotify (Linux): Events from renamed files need proper path tracking
81+
82+
By watching both file and parent consistently across platforms, we ensure:
83+
1. Modification events are captured directly from the file
84+
2. Rename/delete events are captured from parent directory changes
85+
3. Path mismatches in event handling are avoided
8386
*/
8487
let file_path = watch_path.clone();
8588
if let Some(parent) = watch_path.parent() {
8689
if !is_special_fs_path {
87-
// Normal case: watch parent directory (and on kqueue, also the file)
90+
// Normal case: watch both file and parent directory
8891
// clippy::assigning_clones added with Rust 1.78
8992
// Rust version = 1.76 on OpenBSD stable/7.5
9093
#[cfg_attr(not(target_os = "openbsd"), allow(clippy::assigning_clones))]
9194
if parent.is_dir() {
92-
#[cfg(target_os = "linux")]
93-
{
94-
// Linux inotify: watch parent only
95-
watch_path = parent.to_owned();
96-
}
97-
#[cfg(not(target_os = "linux"))]
98-
{
99-
// macOS/BSD kqueue: watch the file first for modification events,
100-
// then also watch parent for rename/delete detection.
101-
// kqueue doesn't send child file modification events when only
102-
// watching the parent directory, unlike Linux inotify.
103-
if let Err(e) = self.watch(&file_path, RecursiveMode::NonRecursive) {
104-
let err_str = e.to_string();
105-
if !is_special_fs_path
106-
|| !(err_str.contains("Operation not supported")
107-
|| err_str.contains("not supported by device")
108-
|| err_str.contains("Not supported")
109-
|| err_str.contains("Permission denied"))
110-
{
111-
return Err(e);
112-
}
95+
// Watch the file first for modification events
96+
if let Err(e) = self.watch(&file_path, RecursiveMode::NonRecursive) {
97+
let err_str = e.to_string();
98+
if !is_special_fs_path
99+
|| !(err_str.contains("Operation not supported")
100+
|| err_str.contains("not supported by device")
101+
|| err_str.contains("Not supported")
102+
|| err_str.contains("Permission denied"))
103+
{
104+
return Err(e);
113105
}
114-
// Also watch parent for rename/delete detection
115-
watch_path = parent.to_owned();
116106
}
107+
// Also watch parent for rename/delete detection
108+
watch_path = parent.to_owned();
117109
} else {
118110
watch_path = PathBuf::from(".");
119111
}

0 commit comments

Comments
 (0)