diff --git a/src/uu/tail/src/tail.rs b/src/uu/tail/src/tail.rs index b6da02264eb..f5fe4215dd5 100644 --- a/src/uu/tail/src/tail.rs +++ b/src/uu/tail/src/tail.rs @@ -469,7 +469,11 @@ fn bounded_tail(file: &mut File, settings: &Settings) -> UResult<()> { file.seek(SeekFrom::End(0)).unwrap(); } FilterMode::Bytes(Signum::Negative(count)) => { - if file.seek(SeekFrom::End(-(*count as i64))).is_err() { + // A count above `i64::MAX` has no negative counterpart to seek by, + // and reaches further back than any file can be long, so treat it + // like any other offset landing before the start of the file. + let offset = i64::try_from(*count).ok(); + if offset.is_none_or(|offset| file.seek(SeekFrom::End(-offset)).is_err()) { file.seek(SeekFrom::Start(0)).unwrap(); } limit = Some(*count); diff --git a/tests/by-util/test_tail.rs b/tests/by-util/test_tail.rs index cec3e2c1d89..6bfeccd8302 100644 --- a/tests/by-util/test_tail.rs +++ b/tests/by-util/test_tail.rs @@ -1377,6 +1377,20 @@ fn test_positive_bytes_file_offset_past_seek_limit() { .no_stdout(); } +// `tail -c -N` on a seekable file larger than the block size seeks back `N` +// bytes from the end. An `N` that does not fit in an `i64` must not abort the +// process; it should print the whole file, like any other too-large count. +#[test] +fn test_negative_bytes_file_count_past_seek_limit() { + let (at, mut ucmd) = at_and_ucmd!(); + // Larger than tail's `sane_blksize` (~4 KiB) so the seek path is taken. + let data = "a".repeat(8192); + at.write("big", &data); + ucmd.args(&["-c", "-9223372036854775808", "big"]) + .succeeds() + .stdout_is(&data); +} + #[test] fn test_num_with_undocumented_sign_bytes() { // tail: '-' is not documented (8.32 man pages)