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
31 changes: 15 additions & 16 deletions src/uu/tail/src/chunks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

use std::collections::VecDeque;
use std::fs::File;
use std::io::{BufRead, Read, Seek, SeekFrom, Write};
use std::io::{self, BufRead, Read, Seek, SeekFrom, Write};
use uucore::error::UResult;

/// When reading files in reverse in `bounded_tail`, this is the size of each
Expand Down Expand Up @@ -46,26 +46,26 @@ pub struct ReverseChunks<'a> {
}

impl<'a> ReverseChunks<'a> {
pub fn new(file: &'a mut File) -> Self {
pub fn new(file: &'a mut File) -> io::Result<Self> {
let current = if cfg!(unix) {
file.stream_position().unwrap()
file.stream_position()?
} else {
0
};
let size = file.seek(SeekFrom::End(0)).unwrap() - current;
let size = file.seek(SeekFrom::End(0))? - current;
let max_blocks_to_read = (size as f64 / BLOCK_SIZE as f64).ceil() as usize;
let block_idx = 0;
ReverseChunks {
Ok(ReverseChunks {
file,
size,
max_blocks_to_read,
block_idx,
}
})
}
}

impl Iterator for ReverseChunks<'_> {
type Item = Vec<u8>;
type Item = io::Result<Vec<u8>>;

fn next(&mut self) -> Option<Self::Item> {
// If there are no more chunks to read, terminate the iterator.
Expand All @@ -85,20 +85,19 @@ impl Iterator for ReverseChunks<'_> {
// Seek backwards by the next chunk, read the full chunk into
// `buf`, and then seek back to the start of the chunk again.
let mut buf = vec![0; block_size as usize];
let pos = self
.file
.seek(SeekFrom::Current(-(block_size as i64)))
.unwrap();
self.file.read_exact(&mut buf).unwrap();
let pos2 = self
let result = self
.file
.seek(SeekFrom::Current(-(block_size as i64)))
.unwrap();
assert_eq!(pos, pos2);
.and_then(|pos| {
self.file.read_exact(&mut buf)?;
let pos2 = self.file.seek(SeekFrom::Current(-(block_size as i64)))?;
assert_eq!(pos, pos2);
Ok(buf)
});

self.block_idx += 1;

Some(buf)
Some(result)
}
}

Expand Down
64 changes: 42 additions & 22 deletions src/uu/tail/src/tail.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
use std::io::{self, BufReader, BufWriter, ErrorKind, Read, Seek, SeekFrom, Write, stdin, stdout};
use std::path::{Path, PathBuf};
use uucore::display::Quotable;
use uucore::error::{FromIo, UResult, USimpleError, set_exit_code};
use uucore::error::{FromIo, UError, UResult, USimpleError, set_exit_code, strip_errno};
use uucore::translate;

use uucore::{show, show_error};
Expand Down Expand Up @@ -178,7 +178,7 @@
&& file.is_seekable(if input.is_stdin() { offset } else { 0 })
&& (!st.is_file() || st.len() > blksize_limit)
{
bounded_tail(&mut file, settings)?;
bounded_tail(&mut file, settings, input.display_name.as_str())?;
reader = BufReader::new(file);
} else {
reader = BufReader::new(file);
Expand Down Expand Up @@ -406,16 +406,17 @@
/// Iterate over bytes in the file, in reverse, until we find the
/// `num_delimiters` instance of `delimiter`. The `file` is left seek'd to the
/// position just after that delimiter.
fn backwards_thru_file(file: &mut File, num_delimiters: u64, delimiter: u8) {
fn backwards_thru_file(file: &mut File, num_delimiters: u64, delimiter: u8) -> io::Result<()> {
if num_delimiters == 0 {
file.seek(SeekFrom::End(0)).unwrap();
return;
file.seek(SeekFrom::End(0))?;
return Ok(());
}
// This variable counts the number of delimiters found in the file
// so far (reading from the end of the file toward the beginning).
let mut counter = 0;
let mut first_slice = true;
for slice in ReverseChunks::new(file) {
for slice in ReverseChunks::new(file)? {
let slice = slice?;
// Iterate over each byte in the slice in reverse order.
let mut iter = memrchr_iter(delimiter, &slice);

Expand All @@ -440,37 +441,34 @@
// cursor in the file is at the *beginning* of the
// block, so seeking forward by `i + 1` bytes puts
// us right after the found delimiter.
file.seek(SeekFrom::Current((i + 1) as i64)).unwrap();
return;
file.seek(SeekFrom::Current((i + 1) as i64))?;
return Ok(());
}
}
}
Ok(())
}

/// When tail'ing a file, we do not need to read the whole file from start to
/// finish just to find the last n lines or bytes. Instead, we can seek to the
/// end of the file, and then read the file "backwards" in blocks of size
/// `BLOCK_SIZE` until we find the location of the first line/byte. This ends up
/// being a nice performance win for very large files.
fn bounded_tail(file: &mut File, settings: &Settings) -> UResult<()> {
debug_assert!(!settings.presume_input_pipe);
/// Seek `file` to where `bounded_tail` should start reading from, returning
/// the byte limit for [`FilterMode::Bytes`]' negative case, if any.
fn seek_to_tail_start(file: &mut File, settings: &Settings) -> io::Result<Option<u64>> {
let mut limit = None;

// Find the position in the file to start printing from.
match &settings.mode {
FilterMode::Lines(Signum::Negative(count), delimiter) => {
backwards_thru_file(file, *count, *delimiter);
backwards_thru_file(file, *count, *delimiter)?;
}
FilterMode::Lines(Signum::Positive(count), delimiter) if count > &1 => {
let i = forwards_thru_file(file, *count - 1, *delimiter).unwrap();
file.seek(SeekFrom::Start(i as u64)).unwrap();
let i = forwards_thru_file(file, *count - 1, *delimiter)?;
file.seek(SeekFrom::Start(i as u64))?;
}
FilterMode::Lines(Signum::MinusZero, _) | FilterMode::Bytes(Signum::MinusZero) => {
file.seek(SeekFrom::End(0)).unwrap();
file.seek(SeekFrom::End(0))?;
}
FilterMode::Bytes(Signum::Negative(count)) => {
if file.seek(SeekFrom::End(-(*count as i64))).is_err() {
file.seek(SeekFrom::Start(0)).unwrap();
file.seek(SeekFrom::Start(0))?;
}
limit = Some(*count);
}
Expand All @@ -478,15 +476,37 @@
// GNU `tail` seems to index bytes and lines starting at 1, not
// at 0. It seems to treat `+0` and `+1` as the same thing.
// A start offset past the largest seekable position makes the
// underlying `lseek` fail with `EINVAL`; treat that like a start

Check warning on line 479 in src/uu/tail/src/tail.rs

View workflow job for this annotation

GitHub Actions / Style/spelling (ubuntu-latest, feat_os_unix)

WARNING: `cspell`: Unknown word 'lseek' (file:'src/uu/tail/src/tail.rs', line:479)
// beyond the end of the file and produce no output.
file.seek(SeekFrom::Start(*count - 1))
.or_else(|_| file.seek(SeekFrom::End(0)))
.unwrap();
.or_else(|_| file.seek(SeekFrom::End(0)))?;
}
_ => {}
}

Ok(limit)
}

/// The message GNU gives for any read/seek failure while tailing a
/// (previously successfully opened) file, keyed to the name as displayed
/// rather than the raw error, which is quoted `strip_errno`'s way.
fn tail_io_error(display_name: &str, error: io::Error) -> Box<dyn UError> {
USimpleError::new(
1,
translate!("tail-error-reading-file", "file" => display_name.to_owned(), "error" => strip_errno(&error)),
)
}

/// When tail'ing a file, we do not need to read the whole file from start to
/// finish just to find the last n lines or bytes. Instead, we can seek to the
/// end of the file, and then read the file "backwards" in blocks of size
/// `BLOCK_SIZE` until we find the location of the first line/byte. This ends up
/// being a nice performance win for very large files.
fn bounded_tail(file: &mut File, settings: &Settings, display_name: &str) -> UResult<()> {
debug_assert!(!settings.presume_input_pipe);

let limit = seek_to_tail_start(file, settings).map_err(|e| tail_io_error(display_name, e))?;

print_target_section(file, limit)?;
Ok(())
}
Expand Down
Loading