From 8dd4d7806981457995a290f79fb0291a6c60ad8f Mon Sep 17 00:00:00 2001 From: Christopher Dryden Date: Sat, 10 Jan 2026 07:59:42 +0000 Subject: [PATCH] tail: fix -F to properly track symlinks with changing targets --- src/uu/tail/src/follow/watch.rs | 7 +++++++ tests/by-util/test_tail.rs | 36 +++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/src/uu/tail/src/follow/watch.rs b/src/uu/tail/src/follow/watch.rs index b195ab0a47a..363449ae7a2 100644 --- a/src/uu/tail/src/follow/watch.rs +++ b/src/uu/tail/src/follow/watch.rs @@ -277,6 +277,10 @@ impl Observer { // If `path` is not a tailable file, add its parent to `Watcher`. watcher_rx .watch(path.parent().unwrap(), RecursiveMode::NonRecursive)?; + // Add symlinks to orphans for retry polling (target may not exist) + if path.is_symlink() { + self.orphans.push(path); + } } else { // If there is no parent, add `path` to `orphans`. self.orphans.push(path); @@ -374,6 +378,9 @@ impl Observer { } } self.files.update_metadata(event_path, Some(new_md)); + } else if event_path.is_symlink() && settings.retry { + self.files.reset_reader(event_path); + self.orphans.push(event_path.clone()); } } EventKind::Remove(RemoveKind::File | RemoveKind::Any) diff --git a/tests/by-util/test_tail.rs b/tests/by-util/test_tail.rs index 9d4a270e2b2..852bb1d9ff7 100644 --- a/tests/by-util/test_tail.rs +++ b/tests/by-util/test_tail.rs @@ -5149,3 +5149,39 @@ fn test_debug_flag_with_inotify() { .with_all_output() .stderr_contains("tail: using notification mode"); } + +#[test] +#[cfg(target_os = "linux")] +fn test_follow_dangling_symlink() { + let (at, mut ucmd) = at_and_ucmd!(); + at.symlink_file("target", "link"); + let mut p = ucmd + .args(&["-s.1", "--max-unchanged-stats=1", "-F", "link"]) + .run_no_wait(); + p.delay(500); + at.write("target", "X\n"); + p.delay(500); + p.kill().make_assertion().with_all_output().stdout_is("X\n"); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_follow_symlink_target_change() { + let (at, mut ucmd) = at_and_ucmd!(); + at.write("t1", "A\n"); + at.symlink_file("t1", "link"); + let mut p = ucmd + .args(&["-s.1", "--max-unchanged-stats=1", "-F", "link"]) + .run_no_wait(); + p.delay(500); + at.remove("link"); + at.symlink_file("t2", "link"); + p.delay(500); + at.write("t2", "B\n"); + p.delay(500); + p.kill() + .make_assertion() + .with_all_output() + .stdout_contains("A\n") + .stdout_contains("B\n"); +}