diff mbox series

btrfs: set_page_extent_mapped after read_folio in btrfs_cont_expand

Message ID f6704eb17e95f3f23a49ec7e4807f4fa45192965.1689180044.git.josef@toxicpanda.com (mailing list archive)
State New, archived
Headers show
Series btrfs: set_page_extent_mapped after read_folio in btrfs_cont_expand | expand

Commit Message

Josef Bacik July 12, 2023, 4:44 p.m. UTC
While trying to get the subpage blocksize tests running, I hit the
following panic on generic/476

assertion failed: PagePrivate(page) && page->private, in fs/btrfs/subpage.c:229
------------[ cut here ]------------
kernel BUG at fs/btrfs/subpage.c:229!
Internal error: Oops - BUG: 00000000f2000800 [#1] SMP
CPU: 1 PID: 1453 Comm: fsstress Not tainted 6.4.0-rc7+ #12
Hardware name: QEMU KVM Virtual Machine, BIOS edk2-20230301gitf80f052277c8-26.fc38 03/01/2023
pstate: 61400005 (nZCv daif +PAN -UAO -TCO +DIT -SSBS BTYPE=--)
pc : btrfs_subpage_assert+0xbc/0xf0
lr : btrfs_subpage_assert+0xbc/0xf0
Call trace:
 btrfs_subpage_assert+0xbc/0xf0
 btrfs_subpage_clear_checked+0x38/0xc0
 btrfs_page_clear_checked+0x48/0x98
 btrfs_truncate_block+0x5d0/0x6a8
 btrfs_cont_expand+0x5c/0x528
 btrfs_write_check.isra.0+0xf8/0x150
 btrfs_buffered_write+0xb4/0x760
 btrfs_do_write_iter+0x2f8/0x4b0
 btrfs_file_write_iter+0x1c/0x30
 do_iter_readv_writev+0xc8/0x158
 do_iter_write+0x9c/0x210
 vfs_iter_write+0x24/0x40
 iter_file_splice_write+0x224/0x390
 direct_splice_actor+0x38/0x68
 splice_direct_to_actor+0x12c/0x260
 do_splice_direct+0x90/0xe8
 generic_copy_file_range+0x50/0x90
 vfs_copy_file_range+0x29c/0x470
 __arm64_sys_copy_file_range+0xcc/0x498
 invoke_syscall.constprop.0+0x80/0xd8
 do_el0_svc+0x6c/0x168
 el0_svc+0x50/0x1b0
 el0t_64_sync_handler+0x114/0x120
 el0t_64_sync+0x194/0x198
---[ end trace 0000000000000000 ]---

This happens because during btrfs_cont_expand we'll get a page, set it
as mapped, and if it's not Uptodate we'll read it.  However between the
read and re-locking the page we could have called release_folio() on the
page, but left the page in the file mapping.  release_folio() can clear
the page private, and thus further down we blow up when we go to modify
the subpage bits.  Fix this by putting the set_page_extent_mapped()
after the read.  This is safe because read_folio() will call
set_page_extent_mapped() before it does the read, and then if we clear
page private but leave it on the mapping we're completely safe
re-setting set_page_extent_mapped().  With this patch I can now run
generic/476 without panicing.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
 fs/btrfs/inode.c | 14 +++++++++++---
 1 file changed, 11 insertions(+), 3 deletions(-)

Comments

Christoph Hellwig July 13, 2023, 11:27 a.m. UTC | #1
This feels a bit ugly, but I can't really think of a much better way
to do this, so:

Reviewed-by: Christoph Hellwig <hch@lst.de>

Interestingly I've just started doing sub-page testing recently and
have not hit it yet.
David Sterba July 13, 2023, 5:56 p.m. UTC | #2
On Wed, Jul 12, 2023 at 12:44:12PM -0400, Josef Bacik wrote:
> While trying to get the subpage blocksize tests running, I hit the
> following panic on generic/476
> 
> assertion failed: PagePrivate(page) && page->private, in fs/btrfs/subpage.c:229
> ------------[ cut here ]------------
> kernel BUG at fs/btrfs/subpage.c:229!
> Internal error: Oops - BUG: 00000000f2000800 [#1] SMP
> CPU: 1 PID: 1453 Comm: fsstress Not tainted 6.4.0-rc7+ #12
> Hardware name: QEMU KVM Virtual Machine, BIOS edk2-20230301gitf80f052277c8-26.fc38 03/01/2023
> pstate: 61400005 (nZCv daif +PAN -UAO -TCO +DIT -SSBS BTYPE=--)
> pc : btrfs_subpage_assert+0xbc/0xf0
> lr : btrfs_subpage_assert+0xbc/0xf0
> Call trace:
>  btrfs_subpage_assert+0xbc/0xf0
>  btrfs_subpage_clear_checked+0x38/0xc0
>  btrfs_page_clear_checked+0x48/0x98
>  btrfs_truncate_block+0x5d0/0x6a8
>  btrfs_cont_expand+0x5c/0x528
>  btrfs_write_check.isra.0+0xf8/0x150
>  btrfs_buffered_write+0xb4/0x760
>  btrfs_do_write_iter+0x2f8/0x4b0
>  btrfs_file_write_iter+0x1c/0x30
>  do_iter_readv_writev+0xc8/0x158
>  do_iter_write+0x9c/0x210
>  vfs_iter_write+0x24/0x40
>  iter_file_splice_write+0x224/0x390
>  direct_splice_actor+0x38/0x68
>  splice_direct_to_actor+0x12c/0x260
>  do_splice_direct+0x90/0xe8
>  generic_copy_file_range+0x50/0x90
>  vfs_copy_file_range+0x29c/0x470
>  __arm64_sys_copy_file_range+0xcc/0x498
>  invoke_syscall.constprop.0+0x80/0xd8
>  do_el0_svc+0x6c/0x168
>  el0_svc+0x50/0x1b0
>  el0t_64_sync_handler+0x114/0x120
>  el0t_64_sync+0x194/0x198
> ---[ end trace 0000000000000000 ]---
> 
> This happens because during btrfs_cont_expand we'll get a page, set it
> as mapped, and if it's not Uptodate we'll read it.  However between the
> read and re-locking the page we could have called release_folio() on the
> page, but left the page in the file mapping.  release_folio() can clear
> the page private, and thus further down we blow up when we go to modify
> the subpage bits.  Fix this by putting the set_page_extent_mapped()
> after the read.  This is safe because read_folio() will call
> set_page_extent_mapped() before it does the read, and then if we clear
> page private but leave it on the mapping we're completely safe
> re-setting set_page_extent_mapped().  With this patch I can now run
> generic/476 without panicing.
> 
> Signed-off-by: Josef Bacik <josef@toxicpanda.com>

Added to misc-next, thanks.
diff mbox series

Patch

diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index 3bde49018530..0e4e4afef133 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -4863,9 +4863,6 @@  int btrfs_truncate_block(struct btrfs_inode *inode, loff_t from, loff_t len,
 		ret = -ENOMEM;
 		goto out;
 	}
-	ret = set_page_extent_mapped(page);
-	if (ret < 0)
-		goto out_unlock;
 
 	if (!PageUptodate(page)) {
 		ret = btrfs_read_folio(NULL, page_folio(page));
@@ -4880,6 +4877,17 @@  int btrfs_truncate_block(struct btrfs_inode *inode, loff_t from, loff_t len,
 			goto out_unlock;
 		}
 	}
+
+	/*
+	 * We unlock the page after the io is completed and then re-lock it
+	 * above.  release_folio() could have come in between that and cleared
+	 * PagePrivate(), but left the page in the mapping.  Set the page mapped
+	 * here to make sure it's properly set for the subpage stuff.
+	 */
+	ret = set_page_extent_mapped(page);
+	if (ret < 0)
+		goto out_unlock;
+
 	wait_on_page_writeback(page);
 
 	lock_extent(io_tree, block_start, block_end, &cached_state);