Commit 951c80f8 authored by Qu Wenruo's avatar Qu Wenruo Committed by David Sterba

btrfs: fix double accounting of ordered extent for subpage case in btrfs_invalidapge

Commit dbfdb6d1 ("Btrfs: Search for all ordered extents that could
span across a page") make btrfs_invalidapage() to search all ordered
extents.

The offending code looks like this:

  again:
	  start = page_start;
	  ordered = btrfs_lookup_ordered_range(inode, start, page_end - start + 1);
	  if (ordred) {
		  end = min(page_end,
			    ordered->file_offset + ordered->num_bytes - 1);

		  /* Do the cleanup */

		  start = end + 1;
		  if (start < page_end)
			  goto again;
	  }

The behavior is indeed necessary for the incoming subpage support, but
when it iterates through all the ordered extents, it also resets the
search range @start.

This means, for the following cases, we can double account the ordered
extents, causing its bytes_left underflow:

	Page offset
	0		16K		32K
	|<--- OE 1  --->|<--- OE 2 ---->|

As the first iteration will find ordered extent (OE) 1, which doesn't
cover the full page, thus after cleanup code, we need to retry again.
But again label will reset start to page_start, and we got OE 1 again,
which causes double accounting on OE 1, and cause OE 1's byte_left to
underflow.

This problem can only happen for subpage case, as for regular sectorsize
== PAGE_SIZE case, we will always find a OE ends at or after page end,
thus no way to trigger the problem.

Move the again label after start = page_start.  There will be more
comprehensive rework to convert the open coded loop to a proper while
loop for subpage support.

Fixes: dbfdb6d1 ("Btrfs: Search for all ordered extents that could span across a page")
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>
parent a4559e6f
...@@ -8183,8 +8183,9 @@ static void btrfs_invalidatepage(struct page *page, unsigned int offset, ...@@ -8183,8 +8183,9 @@ static void btrfs_invalidatepage(struct page *page, unsigned int offset,
if (!inode_evicting) if (!inode_evicting)
lock_extent_bits(tree, page_start, page_end, &cached_state); lock_extent_bits(tree, page_start, page_end, &cached_state);
again:
start = page_start; start = page_start;
again:
ordered = btrfs_lookup_ordered_range(inode, start, page_end - start + 1); ordered = btrfs_lookup_ordered_range(inode, start, page_end - start + 1);
if (ordered) { if (ordered) {
found_ordered = true; found_ordered = true;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment