streams: Include the OS error when AutoFile I/O fails #36210

pull pablomartin4btc wants to merge 2 commits into bitcoin:master from pablomartin4btc:streams/autofile-io-error-detail changing 3 files +110 −5
  1. pablomartin4btc commented at 8:08 PM on September 9, 2026: member

    This came up during review of #35492 (wallet: fail dump on incomplete writes), which switches DumpWallet from std::ofstream to AutoFile so a failed write/close/fsync properly aborts the dump. While reviewing that switch, I noticed the underlying AutoFile/BufferedFile exception messages themselves don't carry the actual reason a fread()/fwrite() call failed — only a fixed string like "AutoFile::write: write failed" — the errno that would explain why the call failed is discarded. Any caller that surfaces e.what() to a log or an error message (including the dump path in #35492, once that's wired up) loses that detail.

    This PR captures errno right after the failing call and folds SysErrorString() into the thrown message for:

    • AutoFile::write() / write_buffer()
    • AutoFile::read() (via detail_fread())
    • AutoFile::ignore()
    • BufferedFile::Fill()

    AutoFile::seek() is intentionally left unchanged — forcing a real, portable fseek() failure (as opposed to a null handle or EOF) across this project's CI platforms isn't practical, so it has no equivalent test coverage.

    Two commits: the first adds streams_tests.cpp coverage that asserts the current behavior — the OS error is discarded on a genuine (non-EOF) I/O failure — with a TODO marking the assertion that gets flipped; the second is the fix itself, flipping that same assertion to confirm the OS error is now present.

    Out of scope for this PR: wiring DumpWallet in #35492 to actually surface this detail in its own error message — that PR's dump.cpp doesn't exist in its AutoFile-based form on master yet, so that wiring has to wait for whichever of the two merges first.

  2. DrahtBot commented at 8:08 PM on September 9, 2026: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    <!--006a51241073e994b41acfe9ec718e94-->

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/36210.

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline and AI policy for information on the review process. A summary of reviews will appear here.

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

    LLM Linter (✨ experimental)

    Possible places where named args for integral literals may be used (e.g. func(x, /*named_arg=*/0) in C++, and func(x, named_arg=0) in Python):

    • BufferedFile bf{f, 8, 4} in src/test/streams_tests.cpp

    <sup>2026-09-10 03:13:58</sup>

  3. DrahtBot added the label CI failed on Sep 9, 2026
  4. DrahtBot commented at 9:20 PM on September 9, 2026: contributor

    <!--85328a0da195eb286784d51f73fa0af9-->

    🚧 At least one of the CI tasks failed. <sub>Task Alpine (musl): https://github.com/bitcoin/bitcoin/actions/runs/34399219812/job/102626611981</sub> <sub>LLM reason (✨ experimental): CI failed because the CTest run reported streams_tests as failed (CTest exit status 8).</sub>

    <details><summary>Hints</summary>

    Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

    </details>

  5. test: AutoFile/BufferedFile I/O failures discard the OS error
    AutoFile::write(), write_buffer(), read() (via detail_fread), ignore(),
    and BufferedFile::Fill() all throw a fixed, generic message when the
    underlying fread()/fwrite() fails for a real OS reason (as opposed to
    reaching end-of-file), discarding the errno that explains why.
    
    Force such a failure with a genuine kernel-level I/O error rather than
    relying on a libc-internal check: on POSIX, open the file in the
    correct direction and close its underlying fd out from under the
    FILE*, so the next read/write hits a real EBADF from the kernel; musl's
    own __towrite()/__toread() never touch errno for a same-direction
    mismatch, unlike glibc's, so a libc-internal check isn't reliable here.
    On Windows, opening in the wrong direction already produces a genuine,
    errno-bearing failure.
    
    TODO: flip check_io_failure() to assert the OS error IS present once
    AutoFile/BufferedFile are fixed to include it.
    2b3a3e2123
  6. pablomartin4btc commented at 3:00 AM on September 10, 2026: member

    Fixing the Alpine (musl) CI failure. Root cause: the test forced a "genuine" I/O failure by opening the file in the wrong direction (write to a read-only-opened stream, etc.), which works on every platform in CI except musl. Confirmed from musl's own source (__towrite.c/__toread.c): when it detects a direction mismatch, it sets an internal error flag and returns immediately, without ever touching errno — it never reaches the actual write(2)/read(2) syscall. glibc happens to set errno in that same internal check as a courtesy, which is why this worked everywhere else; musl isn't required to and doesn't.

    Fix: force a genuine kernel-level failure instead of relying on that libc-internal precheck. On POSIX, the file is now opened in the correct direction and its underlying fd is closed out from under the FILE* before the read/write attempt — since the direction is correct, musl's precheck never triggers, so the call reaches the real syscall, which the kernel fails with EBADF and sets errno for, reliably, on every libc. Windows keeps the original wrong-direction technique, since CI already confirmed that produces a genuine, errno-bearing failure there.

  7. pablomartin4btc marked this as a draft on Sep 10, 2026
  8. streams: include the OS error when AutoFile I/O fails
    AutoFile::write(), write_buffer(), read() (via detail_fread), ignore(),
    and BufferedFile::Fill() throw a fixed message on a genuine fread()/
    fwrite() failure (not end-of-file), discarding the errno that explains
    why the call actually failed. Callers surfacing e.what() to logs or
    users lose that detail.
    
    Capture errno right after the failing call and fold SysErrorString()
    into the thrown message, consistent with the rest of the codebase's
    error reporting (lint-locale-dependence.py restricts raw strerror use
    to SysErrorString's own implementation).
    
    Flips check_io_failure()'s assertion from the previous commit to
    confirm the OS error is now present.
    
    AutoFile::seek() is intentionally left unchanged: forcing a real,
    portable fseek() failure (as opposed to a null handle or EOF) across
    this project's CI platforms isn't practical, so it has no equivalent
    test coverage.
    9e939b7207
  9. pablomartin4btc force-pushed on Sep 10, 2026
  10. DrahtBot removed the label CI failed on Sep 10, 2026
  11. pablomartin4btc marked this as ready for review on Sep 10, 2026
  12. pablomartin4btc commented at 4:28 AM on September 10, 2026: member

    -<ins>Updates</ins>:

    • Fixed Alpine (musl) CI failure as described above.

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-09-10 17:50 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me