• Qu Wenruo's avatar
    btrfs: fix wrong block_start calculation for btrfs_drop_extent_map_range() · fe1c6c7a
    Qu Wenruo authored
    [BUG]
    During my extent_map cleanup/refactor, with extra sanity checks,
    extent-map-tests::test_case_7() would not pass the checks.
    
    The problem is, after btrfs_drop_extent_map_range(), the resulted
    extent_map has a @block_start way too large.
    Meanwhile my btrfs_file_extent_item based members are returning a
    correct @disk_bytenr/@offset combination.
    
    The extent map layout looks like this:
    
         0        16K    32K       48K
         | PINNED |      | Regular |
    
    The regular em at [32K, 48K) also has 32K @block_start.
    
    Then drop range [0, 36K), which should shrink the regular one to be
    [36K, 48K).
    However the @block_start is incorrect, we expect 32K + 4K, but got 52K.
    
    [CAUSE]
    Inside btrfs_drop_extent_map_range() function, if we hit an extent_map
    that covers the target range but is still beyond it, we need to split
    that extent map into half:
    
    	|<-- drop range -->|
    		 |<----- existing extent_map --->|
    
    And if the extent map is not compressed, we need to forward
    extent_map::block_start by the difference between the end of drop range
    and the extent map start.
    
    However in that particular case, the difference is calculated using
    (start + len - em->start).
    
    The problem is @start can be modified if the drop range covers any
    pinned extent.
    
    This leads to wrong calculation, and would be caught by my later
    extent_map sanity checks, which checks the em::block_start against
    btrfs_file_extent_item::disk_bytenr + btrfs_file_extent_item::offset.
    
    This is a regression caused by commit c962098c ("btrfs: fix
    incorrect splitting in btrfs_drop_extent_map_range"), which removed the
    @len update for pinned extents.
    
    [FIX]
    Fix it by avoiding using @start completely, and use @end - em->start
    instead, which @end is exclusive bytenr number.
    
    And update the test case to verify the @block_start to prevent such
    problem from happening.
    
    Thankfully this is not going to lead to any data corruption, as IO path
    does not utilize btrfs_drop_extent_map_range() with @skip_pinned set.
    
    So this fix is only here for the sake of consistency/correctness.
    
    CC: stable@vger.kernel.org # 6.5+
    Fixes: c962098c ("btrfs: fix incorrect splitting in btrfs_drop_extent_map_range")
    Reviewed-by: default avatarFilipe Manana <fdmanana@suse.com>
    Signed-off-by: default avatarQu Wenruo <wqu@suse.com>
    Signed-off-by: default avatarDavid Sterba <dsterba@suse.com>
    fe1c6c7a
extent-map-tests.c 25 KB