Skip to content

Commit 05bdc56

Browse files
committed
feat(tail): follow redirected stdin files
Re-enable test_stdin_redirect_file_follow.
1 parent 370ea74 commit 05bdc56

File tree

3 files changed

+143
-36
lines changed

3 files changed

+143
-36
lines changed

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,
@@ -637,6 +663,22 @@ pub fn follow(mut observer: Observer, settings: &Settings) -> UResult<()> {
637663
paths = observer.files.keys().cloned().collect::<Vec<_>>();
638664
}
639665

666+
if observer.stdin_is_tailable() {
667+
if let Some(stdin_path) = observer.stdin_key() {
668+
if observer.files.contains_key(stdin_path) && !paths.iter().any(|p| p == stdin_path)
669+
{
670+
paths.push(stdin_path.clone());
671+
}
672+
} else {
673+
let stdin_path = PathBuf::from(text::DEV_STDIN);
674+
if observer.files.contains_key(stdin_path.as_path())
675+
&& !paths.iter().any(|p| p.is_stdin())
676+
{
677+
paths.push(stdin_path);
678+
}
679+
}
680+
}
681+
640682
// main print loop
641683
for path in &paths {
642684
_read_some = observer.files.tail_file(path, settings.verbose)?;

src/uu/tail/src/tail.rs

Lines changed: 100 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ fn uu_tail(settings: &Settings) -> UResult<()> {
100100
the input file is not a FIFO, pipe, or regular file, it is unspecified whether or
101101
not the -f option shall be ignored.
102102
*/
103-
if !settings.has_only_stdin() || settings.pid != 0 {
103+
if !settings.has_only_stdin() || settings.pid != 0 || observer.stdin_is_tailable() {
104104
follow::follow(observer, settings)?;
105105
}
106106
}
@@ -227,51 +227,85 @@ fn tail_stdin(
227227
}
228228
}
229229

230-
match input.resolve() {
231-
// fifo
232-
Some(path) => {
233-
let mut stdin_offset = 0;
234-
if cfg!(unix) {
235-
// Save the current seek position/offset of a stdin redirected file.
236-
// This is needed to pass "gnu/tests/tail-2/start-middle.sh"
237-
if let Ok(mut stdin_handle) = Handle::stdin() {
238-
if let Ok(offset) = stdin_handle.as_file_mut().stream_position() {
239-
stdin_offset = offset;
240-
}
241-
}
230+
let resolved_stdin = input.resolve().or_else(resolve_stdin_path);
231+
232+
if let Some(ref path) = resolved_stdin {
233+
let mut stdin_is_seekable_file = false;
234+
let mut stdin_offset = 0;
235+
236+
if let Ok(mut stdin_handle) = Handle::stdin() {
237+
if let Ok(offset) = stdin_handle.as_file_mut().stream_position() {
238+
stdin_offset = offset;
239+
}
240+
if let Ok(meta) = stdin_handle.as_file_mut().metadata() {
241+
stdin_is_seekable_file = meta.is_file();
242242
}
243+
}
244+
245+
if !stdin_is_seekable_file {
246+
stdin_is_seekable_file = path.metadata().map(|meta| meta.is_file()).unwrap_or(false);
247+
}
248+
249+
if stdin_is_seekable_file {
243250
tail_file(
244251
settings,
245252
header_printer,
246253
input,
247-
&path,
254+
path,
248255
observer,
249256
stdin_offset,
250257
)?;
258+
259+
observer.set_stdin_is_tailable(true);
260+
observer.set_stdin_key(path.clone());
261+
if settings.follow.is_some() {
262+
observer.add_path(path, input.display_name.as_str(), None, true)?;
263+
}
264+
return Ok(());
251265
}
252-
// pipe
253-
None => {
266+
267+
if path.metadata().map(|meta| meta.is_dir()).unwrap_or(false) {
254268
header_printer.print_input(input);
255-
if paths::stdin_is_bad_fd() {
256-
set_exit_code(1);
257-
show_error!(
258-
"{}",
259-
translate!("tail-error-cannot-fstat", "file" => translate!("tail-stdin-header"), "error" => translate!("tail-bad-fd"))
260-
);
261-
if settings.follow.is_some() {
262-
show_error!(
263-
"{}",
264-
translate!("tail-error-reading-file", "file" => translate!("tail-stdin-header"), "error" => translate!("tail-bad-fd"))
265-
);
266-
}
267-
} else {
268-
let mut reader = BufReader::new(stdin());
269-
unbounded_tail(&mut reader, settings)?;
270-
observer.add_stdin(input.display_name.as_str(), Some(Box::new(reader)), true)?;
271-
}
269+
set_exit_code(1);
270+
show_error!(
271+
"{}",
272+
translate!(
273+
"tail-error-reading-file",
274+
"file" => input.display_name.clone(),
275+
"error" => translate!("tail-is-a-directory")
276+
)
277+
);
278+
return Ok(());
272279
}
273280
}
274281

282+
header_printer.print_input(input);
283+
if paths::stdin_is_bad_fd() {
284+
set_exit_code(1);
285+
show_error!(
286+
"{}",
287+
translate!(
288+
"tail-error-cannot-fstat",
289+
"file" => translate!("tail-stdin-header"),
290+
"error" => translate!("tail-bad-fd")
291+
)
292+
);
293+
if settings.follow.is_some() {
294+
show_error!(
295+
"{}",
296+
translate!(
297+
"tail-error-reading-file",
298+
"file" => translate!("tail-stdin-header"),
299+
"error" => translate!("tail-bad-fd")
300+
)
301+
);
302+
}
303+
} else {
304+
let mut reader = BufReader::new(stdin());
305+
unbounded_tail(&mut reader, settings)?;
306+
observer.add_stdin(input.display_name.as_str(), Some(Box::new(reader)), true)?;
307+
}
308+
275309
Ok(())
276310
}
277311

@@ -529,6 +563,39 @@ where
529563
}
530564
}
531565

566+
#[cfg(windows)]
567+
fn resolve_stdin_path() -> Option<PathBuf> {
568+
use std::os::windows::io::AsRawHandle;
569+
use windows_sys::Win32::Foundation::MAX_PATH;
570+
use windows_sys::Win32::Storage::FileSystem::{FILE_NAME_OPENED, GetFinalPathNameByHandleW};
571+
572+
let handle = std::io::stdin().lock().as_raw_handle();
573+
if handle.is_null() {
574+
return None;
575+
}
576+
577+
let mut buffer = [0u16; MAX_PATH as usize];
578+
let len = unsafe {
579+
GetFinalPathNameByHandleW(
580+
handle,
581+
buffer.as_mut_ptr(),
582+
buffer.len() as u32,
583+
FILE_NAME_OPENED,
584+
)
585+
} as usize;
586+
587+
if len == 0 || len >= buffer.len() {
588+
return None;
589+
}
590+
591+
String::from_utf16(&buffer[..len]).ok().map(PathBuf::from)
592+
}
593+
594+
#[cfg(not(windows))]
595+
fn resolve_stdin_path() -> Option<PathBuf> {
596+
None
597+
}
598+
532599
#[cfg(test)]
533600
mod tests {
534601

tests/by-util/test_tail.rs

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

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

0 commit comments

Comments
 (0)