upstairs: validate subvolume coverage before IO dispatch#1956
Draft
SG-devel wants to merge 3 commits into
Draft
Conversation
Add regression tests for invalid Volume IO requests that start on a valid block but extend past the final covered block. This is a test-only commit that documents the current boundary behavior before changing it. The Volume read, write, and write_unwritten tests cover requests with a valid covered prefix and an out-of-range suffix. The current code does not verify that collected subvolume coverage fully spans the requested IO before dispatch. The write and write_unwritten tests document the most serious failure mode: a request can be partially dispatched to the final subvolume and still return success. The tests also verify that the already-valid final block is not mutated when the full request is out of range. Add a focused regression test for SubVolume::lba_range_coverage() as well. A request that fully covers a subvolume while extending outside it, such as request [1, 5) against subvolume [2, 4), should return Some(2..4), but currently panics while computing coverage.
Make SubVolume::lba_range_coverage compute the half-open range intersection directly, so requests that cover an entire subvolume while extending outside it return the covered range instead of panicking. Validate that subvolume coverage fully spans the requested Volume read/write range before dispatch. This makes partially covered read, write, and write_unwritten requests return OffsetInvalid instead of proceeding with only the covered part of the request. Return OffsetInvalid instead of relying on a final read assertion when the destination buffer is not fully covered.
The helper is meant to return the non-empty intersection of the requested half-open LBA range and the subvolume's half-open LBA range. Add a property test for that contract over small bounded ranges. The property test derives the expected coverage by enumerating covered block numbers, rather than by using the same `max`/`min` formula as the implementation. This keeps the test oracle independent from the implementation strategy. Also add an explicit overflow case for the requested range end.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
For requests that start on a valid block but extend past the end of the volume, the existing code finds the valid prefix of the request, but does not appear to check that the entire requested range is covered before dispatch.
I am marking this as a draft because it is a boundary-condition change in storage range handling, and I would appreciate confirmation that returning
OffsetInvalidfor partially covered volume IO is the intended contract. I also want to get the CI signal before marking this ready for review.This draft PR proposes a defensive range-validation in
upstairs/src/volume.rs. I tried to keep the change narrow:read,write, andwrite_unwrittenrequests returnOffsetInvalid;Semantic changes
This PR intends the following behavior changes:
SubVolume::lba_range_coverage()now computes the intersection of two half-open ranges directly, using checked arithmetic for the requested range end.SubVolume::lba_range_coverage()returns coverage for requests that fully cover a subvolume while extending outside it, instead of panicking.Volume::write()andVolume::write_unwritten()now reject partially covered requests before dispatching any write.Volume::read()now rejects partially covered requests on the subvolume-backed path before dispatching any subvolume read.OffsetInvalidas a defensive fallback instead of relying only onassert_eq!.Details
The tests build a small volume with two subvolumes:
This setup is intentionally small, but it still exercises the multi-subvolume coverage path used by
Volume.For writes, the important boundary case is a request that starts at the final valid block and extends past the end:
The old write path could treat the valid prefix
[3, 4)as sufficient, dispatch that one-block write, and return success for the full two-block request. This PR changes the write path to verify full coverage before dispatch, so the request is rejected before block3is modified.For reads, the two-subvolume setup also exposes a related coverage-calculation issue:
This request has valid covered pieces, but it is still not fully covered by the volume. It should return
OffsetInvalid.The same read case also exercises
SubVolume::lba_range_coverage()with this shape:That is a valid case where the request fully covers the subvolume while extending outside it. The helper should return
Some(2..4), not panic.The proposal simplifies
SubVolume::lba_range_coverage()to compute the intersection of two half-open ranges directly. On the subvolume-backed IO paths,Volumethen checks that the collected coverage ranges exactly span the requested IO range before dispatching subvolume reads or writes. There is an explicit regression test for the request shape that previously reached the helper's panic path, and there is a property test that checks the helper against an independently derived block-by-block intersection over small bounded ranges. The property test is intended to document that the helper returns exactly the non-empty intersection of the requested half-open LBA range and the subvolume's half-open LBA range.Testing
I ran the following locally on Ubuntu under WSL on Windows:
cargo fmt --checkcargo check --workspacecargo check --workspace --releasecargo test --workspacecargo test --workspace --releaseContribution
I submit this contribution under the repository’s existing license terms, the Mozilla Public License Version 2.0, without any warranty.
AI assistance disclosure: I used ChatGPT (GPT-5.5 Thinking) throughout development of this patch, including reasoning about the issue, writing code and tests, reviewing the implementation, and writing commit and PR text. I guided the work and ran the listed checks.