tail: fix --pid with FIFO by using non-blocking open (#9663)

* tail: fix --pid with FIFO by using non-blocking open

* Address review comments: propagate fcntl errors and remove Windows stub

---------

Co-authored-by: Sylvestre Ledru <sylvestre@debian.org>
Co-authored-by: Sylvestre Ledru <sylvestre.ledru@gmail.com>
This commit is contained in:
Chris Dryden
2026-01-10 06:39:14 -05:00
committed by GitHub
parent bb88fb2de7
commit 3441de92f2
5 changed files with 87 additions and 1 deletions
+1
View File
@@ -217,4 +217,5 @@ TUNABLES
tunables
VMULL
vmull
SETFL
tmpfs
Generated
+1
View File
@@ -3981,6 +3981,7 @@ dependencies = [
"fluent",
"libc",
"memchr",
"nix",
"notify",
"rstest",
"same-file",
+3
View File
@@ -27,6 +27,9 @@ uucore = { workspace = true, features = ["fs", "parser-size", "signals"] }
same-file = { workspace = true }
fluent = { workspace = true }
[target.'cfg(unix)'.dependencies]
nix = { workspace = true, features = ["fs"] }
[target.'cfg(windows)'.dependencies]
windows-sys = { workspace = true, features = [
"Win32_System_Threading",
+43 -1
View File
@@ -152,7 +152,12 @@ fn tail_file(
}
observer.add_bad_path(path, input.display_name.as_str(), false)?;
} else {
match File::open(path) {
#[cfg(unix)]
let open_result = open_file(path, settings.pid != 0);
#[cfg(not(unix))]
let open_result = File::open(path);
match open_result {
Ok(mut file) => {
let st = file.metadata()?;
let blksize_limit = uucore::fs::sane_blksize::sane_blksize_from_metadata(&st);
@@ -197,6 +202,43 @@ fn tail_file(
Ok(())
}
/// Opens a file, using non-blocking mode for FIFOs when `use_nonblock_for_fifo` is true.
///
/// When opening a FIFO with `--pid`, we need to use O_NONBLOCK so that:
/// 1. The open() call doesn't block waiting for a writer
/// 2. We can periodically check if the monitored process is still alive
///
/// After opening, we clear O_NONBLOCK so subsequent reads block normally.
/// Without `--pid`, FIFOs block on open() until a writer connects (GNU behavior).
#[cfg(unix)]
fn open_file(path: &Path, use_nonblock_for_fifo: bool) -> std::io::Result<File> {
use nix::fcntl::{FcntlArg, OFlag, fcntl};
use std::fs::OpenOptions;
use std::os::fd::AsFd;
use std::os::unix::fs::{FileTypeExt, OpenOptionsExt};
let is_fifo = path
.metadata()
.ok()
.is_some_and(|m| m.file_type().is_fifo());
if is_fifo && use_nonblock_for_fifo {
let file = OpenOptions::new()
.read(true)
.custom_flags(libc::O_NONBLOCK)
.open(path)?;
// Clear O_NONBLOCK so reads block normally
let flags = fcntl(file.as_fd(), FcntlArg::F_GETFL)?;
let new_flags = OFlag::from_bits_truncate(flags) & !OFlag::O_NONBLOCK;
fcntl(file.as_fd(), FcntlArg::F_SETFL(new_flags))?;
Ok(file)
} else {
File::open(path)
}
}
fn tail_stdin(
settings: &Settings,
header_printer: &mut HeaderPrinter,
+39
View File
@@ -2659,6 +2659,45 @@ fn test_fifo() {
}
}
/// Test that tail with --pid exits when the monitored process dies, even with a FIFO.
/// Without non-blocking FIFO open, tail would block forever waiting for a writer.
#[test]
#[cfg(all(
not(target_vendor = "apple"),
not(target_os = "windows"),
not(target_os = "android"),
not(target_os = "freebsd"),
not(target_os = "openbsd")
))]
fn test_fifo_with_pid() {
use std::process::Command;
let (at, mut ucmd) = at_and_ucmd!();
at.mkfifo("FIFO");
let mut dummy = Command::new("sh").spawn().unwrap();
let pid = dummy.id();
let mut child = ucmd
.arg("-f")
.arg(format!("--pid={pid}"))
.arg("FIFO")
.run_no_wait();
child.make_assertion_with_delay(500).is_alive();
kill(Pid::from_raw(i32::try_from(pid).unwrap()), Signal::SIGUSR1).unwrap();
let _ = dummy.wait();
child
.make_assertion_with_delay(DEFAULT_SLEEP_INTERVAL_MILLIS)
.is_not_alive()
.with_all_output()
.no_stderr()
.no_stdout()
.success();
}
#[test]
#[cfg(unix)]
#[ignore = "disabled until fixed"]