diff --git a/src/uu/tail/src/tail.rs b/src/uu/tail/src/tail.rs index db1b6a5456..25a467fc51 100644 --- a/src/uu/tail/src/tail.rs +++ b/src/uu/tail/src/tail.rs @@ -480,7 +480,10 @@ fn bounded_tail(file: &mut File, settings: &Settings) -> UResult<()> { FilterMode::Bytes(Signum::Positive(count)) if count > &1 => { // 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. - file.seek(SeekFrom::Start(*count - 1)).unwrap(); + // Clamp to the file length so a start past EOF (or above `i64::MAX`) yields empty + // output instead of panicking on an `EINVAL` seek (#13887). + let len = file.metadata()?.len(); + file.seek(SeekFrom::Start((*count - 1).min(len)))?; } _ => {} } diff --git a/tests/by-util/test_tail.rs b/tests/by-util/test_tail.rs index 985d3702ab..60c2198b38 100644 --- a/tests/by-util/test_tail.rs +++ b/tests/by-util/test_tail.rs @@ -857,6 +857,22 @@ fn test_bytes_single() { .stdout_is_fixture("foobar_bytes_single.expected"); } +#[test] +fn test_positive_bytes_overflowing_offset_does_not_panic() { + // Regression test for #13887: `tail -c +N` on a regular file larger than the block size seeks + // to byte N-1. A very large N (greater than `i64::MAX`) makes the seek fail with `EINVAL`; + // `tail` used to `.unwrap()` and abort. That start is past the end of the file, so the output + // must simply be empty. + let (at, mut ucmd) = at_and_ucmd!(); + // Larger than sane_blksize (~4 KiB) so the seek code path is taken. + at.write("big", &"x".repeat(8192)); + ucmd.arg("-c") + .arg("+18446744073709551615") // u64::MAX + .arg("big") + .succeeds() + .no_output(); +} + #[test] fn test_bytes_stdin() { new_ucmd!()