Skip to content

tail: do not abort on a -c +N offset past the end of the file - #13901

Open
AlejandroCoronadoN wants to merge 1 commit into
uutils:mainfrom
AlejandroCoronadoN:fix-tail-c-plus-seek-panic
Open

tail: do not abort on a -c +N offset past the end of the file#13901
AlejandroCoronadoN wants to merge 1 commit into
uutils:mainfrom
AlejandroCoronadoN:fix-tail-c-plus-seek-panic

Conversation

@AlejandroCoronadoN

Copy link
Copy Markdown

Closes #13887.

Problem

$ yes | head -c 8192 > big
$ tail -c +18446744073709551615 big
thread 'main' panicked ... called `Result::unwrap()` on an `Err` value ...
$ echo $?
134

bounded_tail does file.seek(SeekFrom::Start(*count - 1)).unwrap() for a
positive byte offset. When the offset is past the end of the file and not
seekable (here it exceeds i64::MAX), the seek returns an error and the unwrap
aborts. GNU tail prints nothing for a start offset past end of file and exits
0.

Fix

Mirror the existing negative-bytes arm: if the forward seek fails, fall back to
SeekFrom::End(0) so the following copy reads nothing, instead of unwrapping.

Verification

$ tail -c +18446744073709551615 big ; echo $?   # was 134 (abort), now 0, no output
0

Compared against GNU tail over offsets +18446744073709551615,
+17592186040322, +8193, +100: exit codes and byte counts match. Added a
regression test; the full test_tail suite (122 tests) passes and cargo fmt
/ cargo clippy are clean.

@AlejandroCoronadoN
AlejandroCoronadoN force-pushed the fix-tail-c-plus-seek-panic branch from 163202f to 2592029 Compare August 13, 2026 02:47
@codspeed-hq

codspeed-hq Bot commented Aug 13, 2026

Copy link
Copy Markdown

Merging this PR will improve performance by 3.21%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 1 improved benchmark
✅ 352 untouched benchmarks
⏩ 50 skipped benchmarks1

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Simulation ls_recursive_balanced_tree[(6, 4, 15)] 120.7 ms 117 ms +3.21%

Tip

Curious why this is faster? Comment @codspeedbot explain why this is faster on this PR, or directly use the CodSpeed MCP with your agent.


Comparing AlejandroCoronadoN:fix-tail-c-plus-seek-panic (3f5f713) with main (d8bee62)

Open in CodSpeed

Footnotes

  1. 50 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@sylvestre

Copy link
Copy Markdown
Contributor

please fix the lint:

error: this `if` can be collapsed into the outer `match`
     --> src/uu/tail/src/tail.rs:487:13
      |
  487 | /             if file.seek(SeekFrom::Start(*count - 1)).is_err() {
  488 | |                 file.seek(SeekFrom::End(0))?;
  489 | |             }
      | |_____________^
      |
      = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.97.0/index.html#collapsible_match
      = note: `-D clippy::collapsible-match` implied by `-D warnings`
      = help: to override `-D warnings` add `#[allow(clippy::collapsible_match)]`
  help: collapse nested if block
      |
  480 ~         FilterMode::Bytes(Signum::Positive(count)) if count > &1
  481 |             // GNU `tail` seems to index bytes and lines starting at 1, not
  ...
  486 |             // than aborting on the failed seek.
  487 ~             && file.seek(SeekFrom::Start(*count - 1)).is_err() => {
  488 |                 file.seek(SeekFrom::End(0))?;
  489 ~             }
      |
  

@sylvestre
sylvestre requested a lite review from Copilot August 13, 2026 06:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Prevents tail -c +N from panicking when the requested start offset is past EOF and the underlying seek fails (notably for offsets exceeding i64::MAX), matching GNU tail behavior.

Changes:

  • Replace an unwrap() on a forward seek with a graceful fallback to seeking to EOF when the seek fails.
  • Add a regression test covering very large positive byte offsets past end-of-file.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/uu/tail/src/tail.rs Avoids aborting on failed forward seek by falling back to EOF for -c +N.
tests/by-util/test_tail.rs Adds a regression test ensuring large -c +N offsets do not crash and produce no output.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/uu/tail/src/tail.rs Outdated
Comment on lines +487 to +489
if file.seek(SeekFrom::Start(*count - 1)).is_err() {
file.seek(SeekFrom::End(0))?;
}
Comment on lines +1128 to +1129
at.write("f", &"a".repeat(1_000_000));
ucmd.args(&["-c", "+18446744073709551615", "f"])
@github-actions

Copy link
Copy Markdown

GNU testsuite comparison:

Skip an intermittent issue tests/cut/bounded-memory (fails in this run but passes in the 'main' branch)
Skip an intermittent issue tests/tail/tail-n0f (fails in this run but passes in the 'main' branch)
Congrats! The gnu test tests/cat/splice is no longer failing!
Congrats! The gnu test tests/cut/mb-non-utf8 is no longer failing!
Congrats! The gnu test tests/ls/stat-free-symlinks is no longer failing!
Congrats! The gnu test tests/mv/dir2dir is no longer failing!
Congrats! The gnu test tests/mv/mv-exchange is no longer failing!
Congrats! The gnu test tests/nl/multibyte is no longer failing!
Congrats! The gnu test tests/od/od-j is no longer failing!
Note: The gnu test tests/seq/seq-epipe is now being skipped but was previously passing.
Congrats! The gnu test tests/dd/no-allocate is now passing!
Note: The gnu test tests/misc/write-errors was skipped on 'main' but is now failing.

`tail -c +N` seeked to byte N-1 and unwrapped the result. When N-1 is past the
end of the file and not seekable (for example an offset above i64::MAX), the
seek fails and the unwrap aborts the process. GNU tail prints nothing in that
case. Fall back to seeking to the end of the file so no bytes are printed,
matching GNU, instead of panicking.
@AlejandroCoronadoN
AlejandroCoronadoN force-pushed the fix-tail-c-plus-seek-panic branch from 9fa6b88 to 3f5f713 Compare August 13, 2026 16:10
@AlejandroCoronadoN

Copy link
Copy Markdown
Author

Thanks for the review. Fixed the lint by replacing the if/is_err with a .or_else fallback on the seek; clippy and fmt are clean now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

tail panics (aborts) on -c +N with a very large N (seek .unwrap() on EINVAL)

3 participants