Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .vscode/cspell.dictionaries/jargon.wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -215,4 +215,5 @@ TUNABLES
tunables
VMULL
vmull
SETFL
tmpfs
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/uu/tail/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
47 changes: 46 additions & 1 deletion src/uu/tail/src/tail.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,9 @@ fn tail_file(
}
observer.add_bad_path(path, input.display_name.as_str(), false)?;
} else {
match File::open(path) {
let open_result = open_file(path, settings.pid != 0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think we should get ride of open_file for windows
and just call File::open(path)

so, something like

Suggested change
let open_result = open_file(path, settings.pid != 0);
#[cfg(unix)]
let open_result = open_file(path, settings.pid != 0);
#[cfg(not(unix))]
let open_result = File::open(path);

and remove open_file for windows only


match open_result {
Ok(mut file) => {
let st = file.metadata()?;
let blksize_limit = uucore::fs::sane_blksize::sane_blksize_from_metadata(&st);
Expand Down Expand Up @@ -197,6 +199,49 @@ 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
if let Ok(flags) = fcntl(file.as_fd(), FcntlArg::F_GETFL) {
let new_flags = OFlag::from_bits_truncate(flags) & !OFlag::O_NONBLOCK;
let _ = fcntl(file.as_fd(), FcntlArg::F_SETFL(new_flags));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think we should handle the error here, no ?

}

Ok(file)
} else {
File::open(path)
}
}

#[cfg(not(unix))]
fn open_file(path: &Path, _use_nonblock_for_fifo: bool) -> std::io::Result<File> {
File::open(path)
}

fn tail_stdin(
settings: &Settings,
header_printer: &mut HeaderPrinter,
Expand Down
39 changes: 39 additions & 0 deletions tests/by-util/test_tail.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
Loading