Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
44 changes: 43 additions & 1 deletion src/uu/tail/src/tail.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
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

#[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);
Expand Down Expand Up @@ -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,
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