Skip to content
Open
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 src/uu/tail/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ nix = { workspace = true, features = ["fs"] }

[target.'cfg(windows)'.dependencies]
windows-sys = { workspace = true, features = [
"Win32_Storage_FileSystem",
"Win32_System_Threading",
"Win32_Foundation",
] }
Expand Down
41 changes: 40 additions & 1 deletion src/uu/tail/src/follow/watch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ pub struct Observer {
/// The [`FollowMode`]
pub follow: Option<FollowMode>,

/// True when stdin resolves to a tailable file that we should follow.
stdin_is_tailable: bool,

/// Indicates whether to use the fallback `polling` method instead of the
/// platform specific event driven method. Since `use_polling` is subject to
/// change during runtime it is moved out of [`Settings`].
Expand All @@ -100,6 +103,7 @@ pub struct Observer {
pub files: FileHandling,

pub pid: platform::Pid,
stdin_key: Option<PathBuf>,
}

impl Observer {
Expand All @@ -119,14 +123,32 @@ impl Observer {
Self {
retry,
follow,
stdin_is_tailable: false,
use_polling,
watcher_rx: None,
orphans: Vec::new(),
files,
pid,
stdin_key: None,
}
}

pub fn set_stdin_is_tailable(&mut self, value: bool) {
self.stdin_is_tailable = value;
}

pub fn stdin_is_tailable(&self) -> bool {
self.stdin_is_tailable
}

pub fn set_stdin_key<P: Into<PathBuf>>(&mut self, path: P) {
self.stdin_key = Some(path.into());
}

pub fn stdin_key(&self) -> Option<&PathBuf> {
self.stdin_key.as_ref()
}

pub fn from(settings: &Settings) -> Self {
Self::new(
settings.retry,
Expand All @@ -145,11 +167,12 @@ impl Observer {
update_last: bool,
) -> UResult<()> {
if self.follow.is_some() {
let path = if path.is_relative() {
let path = if path.is_relative() && !path.is_stdin() {
std::env::current_dir()?.join(path)
} else {
path.to_owned()
};

let metadata = path.metadata().ok();
self.files.insert(
&path,
Expand Down Expand Up @@ -632,6 +655,22 @@ pub fn follow(mut observer: Observer, settings: &Settings) -> UResult<()> {
paths = observer.files.keys().cloned().collect::<Vec<_>>();
}

if observer.stdin_is_tailable() {
if let Some(stdin_path) = observer.stdin_key() {
if observer.files.contains_key(stdin_path) && !paths.iter().any(|p| p == stdin_path)
{
paths.push(stdin_path.clone());
}
} else {
let stdin_path = PathBuf::from(text::DEV_STDIN);
if observer.files.contains_key(stdin_path.as_path())
&& !paths.iter().any(|p| p.is_stdin())
{
paths.push(stdin_path);
}
}
}

// main print loop
for path in &paths {
_read_some = observer.files.tail_file(path, settings.verbose)?;
Expand Down
131 changes: 99 additions & 32 deletions src/uu/tail/src/tail.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ fn uu_tail(settings: &Settings) -> UResult<()> {
the input file is not a FIFO, pipe, or regular file, it is unspecified whether or
not the -f option shall be ignored.
*/
if !settings.has_only_stdin() || settings.pid != 0 {
if !settings.has_only_stdin() || settings.pid != 0 || observer.stdin_is_tailable() {
follow::follow(observer, settings)?;
}
}
Expand Down Expand Up @@ -277,47 +277,81 @@ fn tail_stdin(
return Ok(());
}

if let Some(path) = input.resolve() {
// fifo
let resolved_stdin = input.resolve().or_else(resolve_stdin_path);

if let Some(ref path) = resolved_stdin {
let mut stdin_is_seekable_file = false;
let mut stdin_offset = 0;
if cfg!(unix) {
// Save the current seek position/offset of a stdin redirected file.
// This is needed to pass "gnu/tests/tail-2/start-middle.sh"
if let Ok(mut stdin_handle) = Handle::stdin() {
if let Ok(offset) = stdin_handle.as_file_mut().stream_position() {
stdin_offset = offset;
}

if let Ok(mut stdin_handle) = Handle::stdin() {
if let Ok(offset) = stdin_handle.as_file_mut().stream_position() {
stdin_offset = offset;
}
if let Ok(meta) = stdin_handle.as_file_mut().metadata() {
stdin_is_seekable_file = meta.is_file();
}
}
tail_file(
settings,
header_printer,
input,
&path,
observer,
stdin_offset,
)?;
} else {
// pipe
header_printer.print_input(input);
if paths::stdin_is_bad_fd() {

if !stdin_is_seekable_file {
stdin_is_seekable_file = path.metadata().map(|meta| meta.is_file()).unwrap_or(false);
}

if stdin_is_seekable_file {
tail_file(
settings,
header_printer,
input,
path,
observer,
stdin_offset,
)?;

observer.set_stdin_is_tailable(true);
observer.set_stdin_key(path.clone());
return Ok(());
}

if path.metadata().map(|meta| meta.is_dir()).unwrap_or(false) {
header_printer.print_input(input);
set_exit_code(1);
show_error!(
"{}",
translate!("tail-error-cannot-fstat", "file" => translate!("tail-stdin-header"), "error" => translate!("tail-bad-fd"))
translate!(
"tail-error-reading-file",
"file" => input.display_name.clone(),
"error" => translate!("tail-is-a-directory")
)
);
if settings.follow.is_some() {
show_error!(
"{}",
translate!("tail-error-reading-file", "file" => translate!("tail-stdin-header"), "error" => translate!("tail-bad-fd"))
);
}
} else {
let mut reader = BufReader::new(stdin());
unbounded_tail(&mut reader, settings)?;
return Ok(());
}
}

header_printer.print_input(input);
if paths::stdin_is_bad_fd() {
set_exit_code(1);
show_error!(
"{}",
translate!(
"tail-error-cannot-fstat",
"file" => translate!("tail-stdin-header"),
"error" => translate!("tail-bad-fd")
)
);
if settings.follow.is_some() {
show_error!(
"{}",
translate!(
"tail-error-reading-file",
"file" => translate!("tail-stdin-header"),
"error" => translate!("tail-bad-fd")
)
);
}
} else {
let mut reader = BufReader::new(stdin());
unbounded_tail(&mut reader, settings)?;
}

Ok(())
}

Expand Down Expand Up @@ -586,6 +620,39 @@ where
}
}

#[cfg(windows)]
fn resolve_stdin_path() -> Option<PathBuf> {
use std::os::windows::io::AsRawHandle;
use windows_sys::Win32::Foundation::MAX_PATH;
use windows_sys::Win32::Storage::FileSystem::{FILE_NAME_OPENED, GetFinalPathNameByHandleW};

let handle = std::io::stdin().lock().as_raw_handle();
if handle.is_null() {
return None;
}

let mut buffer = [0u16; MAX_PATH as usize];
let len = unsafe {
GetFinalPathNameByHandleW(
handle,
buffer.as_mut_ptr(),
buffer.len() as u32,
FILE_NAME_OPENED,
)
} as usize;

if len == 0 || len >= buffer.len() {
return None;
}

String::from_utf16(&buffer[..len]).ok().map(PathBuf::from)
}

#[cfg(not(windows))]
fn resolve_stdin_path() -> Option<PathBuf> {
None
}

#[cfg(test)]
mod tests {

Expand Down
12 changes: 8 additions & 4 deletions tests/by-util/test_tail.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,6 @@ fn test_stdin_redirect_file() {
}

#[test]
// FIXME: the -f test fails with: Assertion failed. Expected 'tail' to be running but exited with status=exit status: 0
#[ignore = "disabled until fixed"]
#[cfg(not(target_vendor = "apple"))] // FIXME: for currently not working platforms
fn test_stdin_redirect_file_follow() {
// $ echo foo > f
Expand All @@ -152,18 +150,24 @@ fn test_stdin_redirect_file_follow() {

let (at, mut ucmd) = at_and_ucmd!();

at.write("f", "foo");
at.write("f", "foo\n");

let mut p = ucmd
.arg("-f")
.set_stdin(File::open(at.plus("f")).unwrap())
.run_no_wait();

p.make_assertion_with_delay(500).is_alive();
at.append("f", "bar\n");

p.make_assertion_with_delay(DEFAULT_SLEEP_INTERVAL_MILLIS)
.with_current_output()
.stdout_only("foo\nbar\n");

p.kill()
.make_assertion()
.with_all_output()
.stdout_only("foo");
.stdout_only("foo\nbar\n");
}

#[test]
Expand Down
Loading