Skip to content

Commit 77da633

Browse files
committed
feat(tail): follow redirected stdin files
Re-enable test_stdin_redirect_file_follow.
1 parent fcf7571 commit 77da633

3 files changed

Lines changed: 143 additions & 35 deletions

File tree

src/uu/tail/src/follow/watch.rs

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@ pub struct Observer {
9191
/// The [`FollowMode`]
9292
pub follow: Option<FollowMode>,
9393

94+
/// True when stdin resolves to a tailable file that we should follow.
95+
stdin_is_tailable: bool,
96+
9497
/// Indicates whether to use the fallback `polling` method instead of the
9598
/// platform specific event driven method. Since `use_polling` is subject to
9699
/// change during runtime it is moved out of [`Settings`].
@@ -101,6 +104,7 @@ pub struct Observer {
101104
pub files: FileHandling,
102105

103106
pub pid: platform::Pid,
107+
stdin_key: Option<PathBuf>,
104108
}
105109

106110
impl Observer {
@@ -120,14 +124,32 @@ impl Observer {
120124
Self {
121125
retry,
122126
follow,
127+
stdin_is_tailable: false,
123128
use_polling,
124129
watcher_rx: None,
125130
orphans: Vec::new(),
126131
files,
127132
pid,
133+
stdin_key: None,
128134
}
129135
}
130136

137+
pub fn set_stdin_is_tailable(&mut self, value: bool) {
138+
self.stdin_is_tailable = value;
139+
}
140+
141+
pub fn stdin_is_tailable(&self) -> bool {
142+
self.stdin_is_tailable
143+
}
144+
145+
pub fn set_stdin_key<P: Into<PathBuf>>(&mut self, path: P) {
146+
self.stdin_key = Some(path.into());
147+
}
148+
149+
pub fn stdin_key(&self) -> Option<&PathBuf> {
150+
self.stdin_key.as_ref()
151+
}
152+
131153
pub fn from(settings: &Settings) -> Self {
132154
Self::new(
133155
settings.retry,
@@ -146,11 +168,15 @@ impl Observer {
146168
update_last: bool,
147169
) -> UResult<()> {
148170
if self.follow.is_some() {
149-
let path = if path.is_relative() {
171+
#[cfg(unix)]
172+
let path = if path.is_relative() && !path.is_stdin() {
150173
std::env::current_dir()?.join(path)
151174
} else {
152175
path.to_owned()
153176
};
177+
#[cfg(not(unix))]
178+
let path = path.to_owned();
179+
154180
let metadata = path.metadata().ok();
155181
self.files.insert(
156182
&path,
@@ -624,6 +650,22 @@ pub fn follow(mut observer: Observer, settings: &Settings) -> UResult<()> {
624650
paths = observer.files.keys().cloned().collect::<Vec<_>>();
625651
}
626652

653+
if observer.stdin_is_tailable() {
654+
if let Some(stdin_path) = observer.stdin_key() {
655+
if observer.files.contains_key(stdin_path) && !paths.iter().any(|p| p == stdin_path)
656+
{
657+
paths.push(stdin_path.clone());
658+
}
659+
} else {
660+
let stdin_path = PathBuf::from(text::DEV_STDIN);
661+
if observer.files.contains_key(stdin_path.as_path())
662+
&& !paths.iter().any(|p| p.is_stdin())
663+
{
664+
paths.push(stdin_path);
665+
}
666+
}
667+
}
668+
627669
// main print loop
628670
for path in &paths {
629671
_read_some = observer.files.tail_file(path, settings.verbose)?;

src/uu/tail/src/tail.rs

Lines changed: 100 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ fn uu_tail(settings: &Settings) -> UResult<()> {
9898
the input file is not a FIFO, pipe, or regular file, it is unspecified whether or
9999
not the -f option shall be ignored.
100100
*/
101-
if !settings.has_only_stdin() || settings.pid != 0 {
101+
if !settings.has_only_stdin() || settings.pid != 0 || observer.stdin_is_tailable() {
102102
follow::follow(observer, settings)?;
103103
}
104104
}
@@ -267,50 +267,85 @@ fn tail_stdin(
267267
}
268268
}
269269

270-
match input.resolve() {
271-
// fifo
272-
Some(path) => {
273-
let mut stdin_offset = 0;
274-
if cfg!(unix) {
275-
// Save the current seek position/offset of a stdin redirected file.
276-
// This is needed to pass "gnu/tests/tail-2/start-middle.sh"
277-
if let Ok(mut stdin_handle) = Handle::stdin() {
278-
if let Ok(offset) = stdin_handle.as_file_mut().stream_position() {
279-
stdin_offset = offset;
280-
}
281-
}
270+
let resolved_stdin = input.resolve().or_else(resolve_stdin_path);
271+
272+
if let Some(ref path) = resolved_stdin {
273+
let mut stdin_is_seekable_file = false;
274+
let mut stdin_offset = 0;
275+
276+
if let Ok(mut stdin_handle) = Handle::stdin() {
277+
if let Ok(offset) = stdin_handle.as_file_mut().stream_position() {
278+
stdin_offset = offset;
279+
}
280+
if let Ok(meta) = stdin_handle.as_file_mut().metadata() {
281+
stdin_is_seekable_file = meta.is_file();
282282
}
283+
}
284+
285+
if !stdin_is_seekable_file {
286+
stdin_is_seekable_file = path.metadata().map(|meta| meta.is_file()).unwrap_or(false);
287+
}
288+
289+
if stdin_is_seekable_file {
283290
tail_file(
284291
settings,
285292
header_printer,
286293
input,
287-
&path,
294+
path,
288295
observer,
289296
stdin_offset,
290297
)?;
298+
299+
observer.set_stdin_is_tailable(true);
300+
observer.set_stdin_key(path.clone());
301+
if settings.follow.is_some() {
302+
observer.add_path(path, input.display_name.as_str(), None, true)?;
303+
}
304+
return Ok(());
291305
}
292-
// pipe
293-
None => {
306+
307+
if path.metadata().map(|meta| meta.is_dir()).unwrap_or(false) {
294308
header_printer.print_input(input);
295-
if paths::stdin_is_bad_fd() {
296-
set_exit_code(1);
297-
show_error!(
298-
"{}",
299-
translate!("tail-error-cannot-fstat", "file" => translate!("tail-stdin-header"), "error" => translate!("tail-bad-fd"))
300-
);
301-
if settings.follow.is_some() {
302-
show_error!(
303-
"{}",
304-
translate!("tail-error-reading-file", "file" => translate!("tail-stdin-header"), "error" => translate!("tail-bad-fd"))
305-
);
306-
}
307-
} else {
308-
let mut reader = BufReader::new(stdin());
309-
unbounded_tail(&mut reader, settings)?;
310-
}
309+
set_exit_code(1);
310+
show_error!(
311+
"{}",
312+
translate!(
313+
"tail-error-reading-file",
314+
"file" => input.display_name.clone(),
315+
"error" => translate!("tail-is-a-directory")
316+
)
317+
);
318+
return Ok(());
311319
}
312320
}
313321

322+
header_printer.print_input(input);
323+
if paths::stdin_is_bad_fd() {
324+
set_exit_code(1);
325+
show_error!(
326+
"{}",
327+
translate!(
328+
"tail-error-cannot-fstat",
329+
"file" => translate!("tail-stdin-header"),
330+
"error" => translate!("tail-bad-fd")
331+
)
332+
);
333+
if settings.follow.is_some() {
334+
show_error!(
335+
"{}",
336+
translate!(
337+
"tail-error-reading-file",
338+
"file" => translate!("tail-stdin-header"),
339+
"error" => translate!("tail-bad-fd")
340+
)
341+
);
342+
}
343+
} else {
344+
let mut reader = BufReader::new(stdin());
345+
unbounded_tail(&mut reader, settings)?;
346+
observer.add_stdin(input.display_name.as_str(), Some(Box::new(reader)), true)?;
347+
}
348+
314349
Ok(())
315350
}
316351

@@ -568,6 +603,39 @@ where
568603
}
569604
}
570605

606+
#[cfg(windows)]
607+
fn resolve_stdin_path() -> Option<PathBuf> {
608+
use std::os::windows::io::AsRawHandle;
609+
use windows_sys::Win32::Foundation::MAX_PATH;
610+
use windows_sys::Win32::Storage::FileSystem::{FILE_NAME_OPENED, GetFinalPathNameByHandleW};
611+
612+
let handle = std::io::stdin().lock().as_raw_handle();
613+
if handle.is_null() {
614+
return None;
615+
}
616+
617+
let mut buffer = [0u16; MAX_PATH as usize];
618+
let len = unsafe {
619+
GetFinalPathNameByHandleW(
620+
handle,
621+
buffer.as_mut_ptr(),
622+
buffer.len() as u32,
623+
FILE_NAME_OPENED,
624+
)
625+
} as usize;
626+
627+
if len == 0 || len >= buffer.len() {
628+
return None;
629+
}
630+
631+
String::from_utf16(&buffer[..len]).ok().map(PathBuf::from)
632+
}
633+
634+
#[cfg(not(windows))]
635+
fn resolve_stdin_path() -> Option<PathBuf> {
636+
None
637+
}
638+
571639
#[cfg(test)]
572640
mod tests {
573641

tests/by-util/test_tail.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,6 @@ fn test_stdin_redirect_file() {
140140
}
141141

142142
#[test]
143-
// FIXME: the -f test fails with: Assertion failed. Expected 'tail' to be running but exited with status=exit status: 0
144-
#[ignore = "disabled until fixed"]
145143
#[cfg(not(target_vendor = "apple"))] // FIXME: for currently not working platforms
146144
fn test_stdin_redirect_file_follow() {
147145
// $ echo foo > f

0 commit comments

Comments
 (0)