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()(viadetail_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.