pub fn plan_block_allocation_excluding<F>(
sb: &Superblock,
groups: &[BlockGroupDescriptor],
count: u32,
hint_group: u32,
reserved: &[u64],
bitmap_reader: F,
) -> Result<BlockAllocationPlan>Expand description
plan_block_allocation, with blocks that are SPOKEN FOR BUT NOT YET
COMMITTED treated as used.
§WHY THE PLANNER HAS TO BE TOLD, RATHER THAN ITS ANSWER FILTERED
A caller that plans several allocations before committing any of
them – which is the late-commit ordering the write paths use so a
failure half way through leaks no blocks – shows this function the
same unchanged bitmap every time, so it returns the same block every
time. Measured on a fresh 64 MiB image, three consecutive plans
against an uncommitted bitmap: 517 517 517.
The caller that hit this defended itself with one equality test
against one reserved block, which is wrong twice: it refuses with
NoSpaceLeftOnDevice on a nearly empty filesystem when the block
does match, and when it does not it lets the SECOND meta block alias
the first, because the test never looked at the ones it had already
handed out. Two extent-tree nodes on one physical block is silent
corruption; the refusal is at least loud.
EXTENDING THE EQUALITY TEST TO THE WHOLE LIST IS NOT THE FIX. The planner would go on returning the same block, so the list would just turn the aliasing into a second spurious refusal. The reservations have to be in the bitmap the scan reads.
§THE OVERLAY CANNOT LIVE IN THE CALLER’S READER
The obvious smaller change – have the caller’s bitmap_reader
return bytes with the reserved bits already set – is not enough,
because a BLOCK_UNINIT group never calls the reader at all: the
bitmap is fabricated as all-free right here. That is exactly the
state a freshly formatted image is in, which is where this is most
likely to bite. So the marking happens after the bitmap is obtained,
on both paths.