mirror of
https://github.com/uutils/coreutils.git
synced 2026-05-06 07:26:38 -04:00
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:
@@ -217,4 +217,5 @@ TUNABLES
|
||||
tunables
|
||||
VMULL
|
||||
vmull
|
||||
SETFL
|
||||
tmpfs
|
||||
|
||||
Generated
+1
@@ -3981,6 +3981,7 @@ dependencies = [
|
||||
"fluent",
|
||||
"libc",
|
||||
"memchr",
|
||||
"nix",
|
||||
"notify",
|
||||
"rstest",
|
||||
"same-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
@@ -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,
|
||||
|
||||
@@ -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"]
|
||||
|
||||
Reference in New Issue
Block a user