Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
There has been a bug in the filesystem for a while where truncating to a block boundary suffers from an off-by-one mistake that corrupts the internal representation of the CTZ skip-list. This mostly appears when the file_size == block_size, as file_size > block_size includes CTZ skip-list metadata, so the underlying block boundaries appear at slightly different offsets. --- The reason for off-by-one issue is a nuance in lfs_ctz_find that we sort of abuse to get two different behaviors. Consider the situation where this bug occurs: block 0 block 1 .--------. .--------. | abcdef |<-| {ptr0} | | ghijkl | | yzabcd | | mnopqr | | | | stuvwx | | | '--------' '--------' With these 24-byte blocks, there's an ambiguity if we wanted to point to offset 24. We could point before the block boundary, or we could point after the block boundary Before: block 0 block 1 .--------. .--------. | abcdef |<-| {ptr0} | | ghijkl | | yzabcd | | mnopqr | | | | stuvwx | | | '-------^' '--------' '-- off=24 is here After: block 0 block 1 .--------. .--------. | abcdef |<-| {ptr0} | | ghijkl | | yzabcd | | mnopqr | | ^ | | stuvwx | | | | '--------' '-|------' '-- off=24 is here When we want these two offsets depends on the context. We want the offset to be conservative if it represents a size, but eager if it is being used to prepare a block for writing. The workaround/hack is to prefer the eager offset, after the block boundary, but use `size-1` as the argument if we need the conservative offset. This finds the correct block, but is off-by-one in the calculated block-offset. Fortunately we happen to not use the block-offset in the places we need this workaround/hack. --- To get back to the bug, the wrong mode of lfs_ctz_find was used in lfs_file_truncate, leading to internal corruption of the CTZ skip-list. The correct behavior is size-1, with care to avoid underflow. Also I've tweaked the code to make it clear the calculated block-offset goes unused in these situations. Thanks to ghost, ajaybhargav, and others for reporting the issue, colin-foster-advantage for a reproducible test case, and rvanschoren, hgspbs for the initial solution.
- Loading branch information