Repository navigation
fileserver: a write refused after allocating gives back the blocks it took - #1120
Conversation
Review of #1120 at 41efb19 (base
|
8c3bfd2 to
eb77322
Compare
8be159d to
bea91a3
Compare
eb77322 to
dacf972
Compare
09203d2 to
1eb1a9f
Compare
… took DATA's write allocates every page past the file's extents and then writes the pages through the cache. When a page's cache write, or the read of a page written in part, failed, the call returned the error with the pages it never wrote still in the extents, past the file's length. Those blocks held what their last file left there. A later extending truncate, or a write beyond them, brought them under the file's length, and since `allocate` zeroes only from the extents' end, a read returned the deleted file's bytes: one file's contents shown to another's reader. The allocation and the copy now feed one result, and any refusal drops the extents back to their length before the call and frees the rest, as a refused allocation already did. Pages written over in place keep what the call wrote, as before. The host test deletes a 512-page file of 0xAB, then writes half of a new file's page 0, whose read the disk refuses, and page 1, allocated first. It extends the file and reads zeros in page 1. Before the fix page 1 read 0xab. Found in review of #1118 (`issues/a-write-that-fails-after-allocating-...`, deleted here as fixed). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ming back The test now asserts the write is refused with `Io`, the refused read's word, and that the volume's free room is the same before and after it. A refusal before allocating, or a leak or a double free, fails it. Every block of the test disk holds 0xAB before the volume is formatted, so whatever block page 1 is given holds stale bytes; the 512-page file and its tie to the allocator's placement are gone. Undoing the drop-back fails the room check. The review's unrelated finding is recorded: a zero-length write past the end extends a DATA file, against POSIX. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dacf972 to
185a190
Compare
…erve took #1120 made DATA's write give back every block it allocated when the copy that followed was refused. With 7a the allocation is `Volume::reserve` and the copy is separate (the paged object's write, or `Writes::write`'s write-backs), so the rebase onto #1120 carries its rule across that split: - `reserve` answers a `Reserved`: the size and the pages the file held blocks for before it. - `Volume::unreserve(node, reserved, landed)` returns a refused write to that size, or to where its bytes landed, and gives back the blocks past both. The default is the cut back to that size, which suffices on a format without holes (FAT). DATA keeps blocks only up to what the file held, since a write into a hole below the old end takes a block a cut to the old size would keep. - `Files::write` and `Writes::write` call it on a refusal; `Files::write` no longer truncates for it. DATA's unreserve records no entry: a file's entry is written only by its own calls (close, rename, truncate, sync), none of which runs inside a write, so nothing named the reserve's blocks. #1120's test is back, through `Writes::write`, beside one for the hole: both fail with DATA's unreserve deleted, and with `Writes::write` giving nothing back. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…erve took #1120 made DATA's write give back every block it allocated when the copy that followed was refused. With 7a the allocation is `Volume::reserve` and the copy is separate (the paged object's write, or `Writes::write`'s write-backs), so the rebase onto #1120 carries its rule across that split: - `reserve` answers a `Reserved`: the size and the pages the file held blocks for before it. - `Volume::unreserve(node, reserved, landed)` returns a refused write to that size, or to where its bytes landed, and gives back the blocks past both. The default is the cut back to that size, which suffices on a format without holes (FAT). DATA keeps blocks only up to what the file held, since a write into a hole below the old end takes a block a cut to the old size would keep. - `Files::write` and `Writes::write` call it on a refusal; `Files::write` no longer truncates for it. DATA's unreserve records no entry: a file's entry is written only by its own calls (close, rename, truncate, sync), none of which runs inside a write, so nothing named the reserve's blocks. #1120's test is back, through `Writes::write`, beside one for the hole: both fail with DATA's unreserve deleted, and with `Writes::write` giving nothing back. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem
Found in review of #1118. DATA's write allocates every page past the file's extents, then writes the pages through the cache. When a page's cache write, or the read of a page written in part, failed, the call returned the error with the pages it never wrote still in the file's extents, past its length. Those blocks held what their last file left there.
A later extending
truncate, or a write beyond them, brought them under the file's length.allocatezeroes only from the extents' end, which already included them, so a read returned a deleted file's bytes: one file's contents shown to another's reader. This predates #1118.Change
DataVolume::write's allocation and its copy feed one result. Any refusal drops the extents back to their length before the call (keep_blocks) and frees the rest (free_extents), as a refused allocation already did. Pages written over in place keep what the call wrote, as before. The record #1118 added is deleted as fixed.Checks
Evidence: https://github.com/ToyOSOrg/evidence/tree/fd1fd531843b009e0d6eb58be1ce2d8522ec19ee
cargo test -p fileserver91 passed.allocatecall carries bcachefs: a write's pages are allocated at once, one bitmap write per block of bits #1118's newmin_runargument.cargo test -p fileserver91 passed;cargo run -- --ci hostexit 0, 107 green.cargo test -p fileserver --release91 passed on the restacked head.Err(Error::Io)and the same free room before and after the refused write. Every block of the test disk holds 0xAB before formatting, so the test no longer depends on placement. Undoing the drop-back fails the room check.cargo test -p fileserver91 passed;--ci hostexit 0, 105 green (evidence https://github.com/ToyOSOrg/evidence/tree/6e7ef4652914fca6e3544d8743aa3d86c6f323c4,r1/). The reviewer's unrelated finding is recorded:issues/a-zero-length-write-past-the-end-extends-a-data-file.md.data::tests::a_write_refused_after_allocating_leaves_no_old_bytes_past_the_end.before-fix.log), exit 101: "page 1 reads 0xab, not zeros".cargo test -p fileserver --release: 91 passed.cargo run -- --ci hostat 41efb19: exit 0, 105 green.🤖 Generated with Claude Code