linux/fs/btrfs/tests
Qu Wenruo fe1c6c7acc btrfs: fix wrong block_start calculation for btrfs_drop_extent_map_range()
[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 c962098ca4 ("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: c962098ca4 ("btrfs: fix incorrect splitting in btrfs_drop_extent_map_range")
Reviewed-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2024-04-18 18:18:50 +02:00
..
btrfs-tests.c btrfs: always set extent_io_tree::inode and drop fs_info 2023-12-15 20:27:02 +01:00
btrfs-tests.h btrfs: use a dedicated data structure for chunk maps 2023-12-15 20:27:02 +01:00
extent-buffer-tests.c btrfs: abort transaction on generation mismatch when marking eb as dirty 2023-10-12 16:44:07 +02:00
extent-io-tests.c btrfs: tests: allocate dummy fs_info and root in test_find_delalloc() 2024-03-04 16:24:49 +01:00
extent-map-tests.c btrfs: fix wrong block_start calculation for btrfs_drop_extent_map_range() 2024-04-18 18:18:50 +02:00
free-space-tests.c btrfs: remove use btrfs_remove_free_space_cache instead of variant 2022-09-26 12:27:58 +02:00
free-space-tree-tests.c btrfs: convert btrfs_block_group::needs_free_space to runtime flag 2022-12-05 18:00:51 +01:00
inode-tests.c btrfs: remove the pg_offset parameter from btrfs_get_extent() 2024-03-04 16:24:45 +01:00
qgroup-tests.c btrfs: use a structure to pass arguments to backref walking functions 2022-12-05 18:00:50 +01:00