Skip to content

cat: report bad file descriptor when stdin is closed - #15253

Open
ozenyunus35 wants to merge 2 commits into
uutils:mainfrom
ozenyunus35:fix/cat-closed-stdin
Open

ozenyunus35 wants to merge 2 commits into
uutils:mainfrom
ozenyunus35:fix/cat-closed-stdin

Conversation

@ozenyunus35

@ozenyunus35 ozenyunus35 commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Closes #15278

Summary

Refs #14705 (misc/close-stdin.sh).

When cat is started with stdin closed, it currently treats the input as empty and exits successfully instead of reporting a bad file descriptor.

This happens because Rust's runtime can reopen a closed stdin as /dev/null before the utility starts.

Changes

  • Use the existing uucore::signals::stdin_was_closed() check to detect closed stdin.
  • Return EBADF when cat attempts to read from a closed stdin.
  • Preserve normal processing of other file operands.
  • Add two regression tests covering closed stdin, including a case with an additional file operand.

Testing

The new regression tests fail before the fix and pass afterward.

Validation completed:

  • All 48 cat tests passed.
  • cargo fmt --check passed.
  • cargo clippy -p uu_cat --all-targets -D warnings passed.
  • Normal piped input and explicit /dev/null input continue to work correctly.

The regression tests are skipped on WASI, where subprocess spawning is unavailable.

Scope

This change addresses the closed-stdin behavior of cat only. Other utilities covered by the GNU compatibility test remain to be addressed separately.

AI Assistance

Claude Code assisted with investigating the issue, implementing the change, and preparing the regression tests. The changes were validated using the project's tests and lint checks.

Rust reopens a closed stdin as /dev/null before uumain runs, so
`cat <&-` read nothing and exited 0. Check whether stdin was closed at
startup, as tac does, and fail with EBADF like reading fd 0 would.
@xtqqczze

Copy link
Copy Markdown
Collaborator

@ozenyunus35 Please open an issue to track this work, then include Closes #XXXXX in the PR description.

@github-actions

Copy link
Copy Markdown

GNU testsuite comparison:

Skipping an intermittent issue tests/date/resolution (passes in this run but fails in the 'main' branch)

Comment thread tests/by-util/test_cat.rs Outdated

#[test]
#[cfg(unix)]
#[cfg_attr(wasi_runner, ignore = "WASI: no subprocess spawning")]

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.

wasi isn't unix, so this ignore is dead code under #[cfg(unix)], no?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I seem to remember this being complicated, wasi_runner runs on a Unix host?

cc: @oech3

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.

Unfortunately, we need to exculude wasi_runner manually since host of test code is still Linux.

But I think we can use #[cfg(not(wasi_runner))] instead of ignore if EBADF is essentially unix specific.

Comment thread tests/by-util/test_cat.rs Outdated
#[test]
#[cfg(unix)]
#[cfg_attr(wasi_runner, ignore = "WASI: no subprocess spawning")]
fn test_closed_stdin_with_file() {

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.

this one already covers the previous test, could we keep only this one?

@ozenyunus35

Copy link
Copy Markdown
Contributor Author

@sylvestre merged the two tests into one and switched to #[cfg(all(unix, not(wasi_runner)))] in 03b6771. I also opened #15278 for this and linked it in the description.

@xtqqczze

Copy link
Copy Markdown
Collaborator

@ozenyunus35 please rebase for a clean commit history. the PR description could also be more concise.

@codspeed

codspeed Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

Merging this PR will degrade performance by 45.81%

⚠️ 122 benchmarks spent significant time in system calls

System calls cannot be consistently instrumented, so they are not included in the measure, which understates the real cost. Please switch to the Walltime instrument to accurately measure system calls.

Measurement and system calls

ℹ️ 1 benchmark has no flame graph

The flamegraph could not be generated because too much time was spent in runtime internals, making the visualization not useful for performance analysis.

⚠️ 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
❌ 1 regressed benchmark
✅ 399 untouched benchmarks
⏩ 54 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
❌ Simulation three_39_bit_primes 490.4 ms 1,722.7 ms -71.53%
⚡ Simulation five_38_bit_primes 1.7 s 1.6 s +3.17%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing ozenyunus35:fix/cat-closed-stdin (03b6771) with main (efca981)2

Open in CodSpeed

Footnotes

  1. 54 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. ↩

  2. No successful run was found on main (d43f2b8) during the generation of this report, so efca981 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report. ↩

This branch has not been deployed

No deployments
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.

cat: reading a closed stdin succeeds instead of failing with EBADF

4 participants