mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
tail: fix stdin redirect when file is not at its beginning
Previously, if stdin redirect pointed to a regular file, tailing started at the beginning of the file. However, tailing needs to start at the current position because this is expected by tests/tail-2/start-middle.sh. This fixes the issue by taking the current offset into account while going backwards through the stdin redirected file.
This commit is contained in:
parent
92c3f60440
commit
942928b0ea
3 changed files with 60 additions and 11 deletions
|
@ -30,7 +30,8 @@ pub struct ReverseChunks<'a> {
|
||||||
|
|
||||||
impl<'a> ReverseChunks<'a> {
|
impl<'a> ReverseChunks<'a> {
|
||||||
pub fn new(file: &'a mut File) -> ReverseChunks<'a> {
|
pub fn new(file: &'a mut File) -> ReverseChunks<'a> {
|
||||||
let size = file.seek(SeekFrom::End(0)).unwrap();
|
let current = file.seek(SeekFrom::Current(0)).unwrap();
|
||||||
|
let size = file.seek(SeekFrom::End(0)).unwrap() - current;
|
||||||
let max_blocks_to_read = (size as f64 / BLOCK_SIZE as f64).ceil() as usize;
|
let max_blocks_to_read = (size as f64 / BLOCK_SIZE as f64).ceil() as usize;
|
||||||
let block_idx = 0;
|
let block_idx = 0;
|
||||||
ReverseChunks {
|
ReverseChunks {
|
||||||
|
|
|
@ -128,6 +128,8 @@ pub struct Settings {
|
||||||
use_polling: bool,
|
use_polling: bool,
|
||||||
verbose: bool,
|
verbose: bool,
|
||||||
stdin_is_pipe_or_fifo: bool,
|
stdin_is_pipe_or_fifo: bool,
|
||||||
|
stdin_offset: u64,
|
||||||
|
stdin_redirect: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Settings {
|
impl Settings {
|
||||||
|
@ -323,6 +325,15 @@ fn uu_tail(mut settings: Settings) -> UResult<()> {
|
||||||
settings.paths.push_front(dash);
|
settings.paths.push_front(dash);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cfg!(unix) && settings.stdin_is_pipe_or_fifo {
|
||||||
|
settings.stdin_redirect = PathBuf::from(text::STDIN_HEADER).handle_redirect();
|
||||||
|
use std::os::unix::io::FromRawFd;
|
||||||
|
let mut stdin_handle = unsafe { std::fs::File::from_raw_fd(0) };
|
||||||
|
if let Ok(offset) = stdin_handle.seek(SeekFrom::Current(0)) {
|
||||||
|
settings.stdin_offset = offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: is there a better way to check for a readable stdin?
|
// TODO: is there a better way to check for a readable stdin?
|
||||||
let mut buf = [0; 0]; // empty buffer to check if stdin().read().is_err()
|
let mut buf = [0; 0]; // empty buffer to check if stdin().read().is_err()
|
||||||
let stdin_read_possible = settings.stdin_is_pipe_or_fifo && stdin().read(&mut buf).is_ok();
|
let stdin_read_possible = settings.stdin_is_pipe_or_fifo && stdin().read(&mut buf).is_ok();
|
||||||
|
@ -341,7 +352,11 @@ fn uu_tail(mut settings: Settings) -> UResult<()> {
|
||||||
// Iterate user provided `paths` and add them to Watcher.
|
// Iterate user provided `paths` and add them to Watcher.
|
||||||
if let Some((ref mut watcher, _)) = watcher_rx {
|
if let Some((ref mut watcher, _)) = watcher_rx {
|
||||||
for path in &settings.paths {
|
for path in &settings.paths {
|
||||||
let mut path = path.handle_redirect();
|
let mut path = if path.is_stdin() {
|
||||||
|
settings.stdin_redirect.to_owned()
|
||||||
|
} else {
|
||||||
|
path.to_owned()
|
||||||
|
};
|
||||||
if path.is_stdin() {
|
if path.is_stdin() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -376,7 +391,11 @@ fn uu_tail(mut settings: Settings) -> UResult<()> {
|
||||||
} else {
|
} else {
|
||||||
path.to_owned()
|
path.to_owned()
|
||||||
};
|
};
|
||||||
let mut path = path.handle_redirect();
|
let path = if path.is_stdin() {
|
||||||
|
settings.stdin_redirect.to_owned()
|
||||||
|
} else {
|
||||||
|
path.to_owned()
|
||||||
|
};
|
||||||
let path_is_tailable = path.is_tailable();
|
let path_is_tailable = path.is_tailable();
|
||||||
|
|
||||||
if !path.is_stdin() && !path_is_tailable {
|
if !path.is_stdin() && !path_is_tailable {
|
||||||
|
@ -479,9 +498,14 @@ fn uu_tail(mut settings: Settings) -> UResult<()> {
|
||||||
files.print_header(&display_name, !first_header);
|
files.print_header(&display_name, !first_header);
|
||||||
first_header = false;
|
first_header = false;
|
||||||
}
|
}
|
||||||
let mut reader;
|
|
||||||
|
|
||||||
if file.is_seekable() && metadata.as_ref().unwrap().get_block_size() > 0 {
|
let mut reader;
|
||||||
|
if file.is_seekable(if display_name.is_stdin() {
|
||||||
|
settings.stdin_offset
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}) && metadata.as_ref().unwrap().get_block_size() > 0
|
||||||
|
{
|
||||||
bounded_tail(&mut file, &settings);
|
bounded_tail(&mut file, &settings);
|
||||||
reader = BufReader::new(file);
|
reader = BufReader::new(file);
|
||||||
} else {
|
} else {
|
||||||
|
@ -513,9 +537,11 @@ fn uu_tail(mut settings: Settings) -> UResult<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if settings.retry && settings.follow.is_some() {
|
} else if settings.retry && settings.follow.is_some() {
|
||||||
if path.is_relative() {
|
let path = if path.is_relative() {
|
||||||
path = std::env::current_dir()?.join(&path);
|
std::env::current_dir()?.join(&path)
|
||||||
}
|
} else {
|
||||||
|
path.to_owned()
|
||||||
|
};
|
||||||
// Insert non-is_tailable() paths into `files.map`
|
// Insert non-is_tailable() paths into `files.map`
|
||||||
files.insert(
|
files.insert(
|
||||||
&path,
|
&path,
|
||||||
|
@ -1544,14 +1570,16 @@ pub fn stdin_is_bad_fd() -> bool {
|
||||||
|
|
||||||
trait FileExtTail {
|
trait FileExtTail {
|
||||||
#[allow(clippy::wrong_self_convention)]
|
#[allow(clippy::wrong_self_convention)]
|
||||||
fn is_seekable(&mut self) -> bool;
|
fn is_seekable(&mut self, current_offset: u64) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileExtTail for File {
|
impl FileExtTail for File {
|
||||||
fn is_seekable(&mut self) -> bool {
|
/// Test if File is seekable.
|
||||||
|
/// Set the current position offset to `current_offset`.
|
||||||
|
fn is_seekable(&mut self, current_offset: u64) -> bool {
|
||||||
self.seek(SeekFrom::Current(0)).is_ok()
|
self.seek(SeekFrom::Current(0)).is_ok()
|
||||||
&& self.seek(SeekFrom::End(0)).is_ok()
|
&& self.seek(SeekFrom::End(0)).is_ok()
|
||||||
&& self.seek(SeekFrom::Start(0)).is_ok()
|
&& self.seek(SeekFrom::Start(current_offset)).is_ok()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -94,6 +94,26 @@ fn test_stdin_redirect_file() {
|
||||||
assert!(buf_stderr.is_empty());
|
assert!(buf_stderr.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(all(unix, not(any(target_os = "android", target_vendor = "apple"))))] // FIXME: make this work not just on Linux
|
||||||
|
fn test_stdin_redirect_offset() {
|
||||||
|
// inspired by: "gnu/tests/tail-2/start-middle.sh"
|
||||||
|
use std::io::{Seek, SeekFrom};
|
||||||
|
|
||||||
|
let ts = TestScenario::new(util_name!());
|
||||||
|
let at = &ts.fixtures;
|
||||||
|
|
||||||
|
at.write("k", "1\n2\n");
|
||||||
|
let mut fh = std::fs::File::open(at.plus("k")).unwrap();
|
||||||
|
fh.seek(SeekFrom::Start(2)).unwrap();
|
||||||
|
|
||||||
|
ts.ucmd()
|
||||||
|
.set_stdin(fh)
|
||||||
|
.run()
|
||||||
|
.stdout_is("2\n")
|
||||||
|
.succeeded();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_nc_0_wo_follow() {
|
fn test_nc_0_wo_follow() {
|
||||||
// verify that -[nc]0 without -f, exit without reading
|
// verify that -[nc]0 without -f, exit without reading
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue