[PATCH 6.6.y] btrfs: fix unfinished ordered extent when cow_file_range() fails

From: Joseph Qi

Date: Mon Nov 09 2026 - 07:53:14 EST


Commit 4ace6fb49c8b ("btrfs: do proper folio cleanup when cow_file_range()
failed") moved the extent and folio unlock out of the allocation loop.
The per-iteration PAGE_SET_ORDERED went with it, and the replacement
after the loop only runs on success, so the range that already has
ordered extents is now cleaned up with PAGE_UNLOCK, PAGE_START_WRITEBACK
and PAGE_END_WRITEBACK only.

Upstream can do that because btrfs_run_delalloc_range() passes NULL to
btrfs_cleanup_ordered_extents(), which then accounts the whole range
without consulting any page bit. Where that function still takes
@locked_page it skips it instead, leaving its share of the ordered
extent to the caller, which finishes it through
btrfs_mark_ordered_io_finished() with the page argument.
can_finish_ordered_extent() returns early when the Ordered bit is not
set, so that share is never accounted and bytes_left never reaches zero.
btrfs_wait_ordered_range() then waits forever, and a later write to the
same offset can reach the -EEXIST btrfs_panic() in
insert_ordered_extent().

Both callers that rely on this are affected: __extent_writepage(), and
submit_uncompressed_range(), the compression fallback, which reaches
cow_file_range() through run_delalloc_cow().

Set PAGE_SET_ORDERED again for that range. The cleanup still skips
@locked_page and accounts the rest with a NULL page, so the two ranges
do not overlap and nothing is accounted twice.

Upstream fixed this differently, in "btrfs: fix double accounting race
when btrfs_run_delalloc_range() failed", which is earlier in the same
series. That commit depends on the writepage_delalloc() rework and the
folio conversion of extent_io.c, so restore the flag here instead.

Fixes: 4ace6fb49c8b ("btrfs: do proper folio cleanup when cow_file_range() failed")
Signed-off-by: Joseph Qi <joseph.qi@xxxxxxxxxxxxxxxxx>
---
fs/btrfs/inode.c | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index c8034d86f844f..a40201596b80f 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -1532,10 +1532,16 @@ static noinline int cow_file_range(struct btrfs_inode *inode,
*
* So here we only clear EXTENT_LOCKED and EXTENT_DELALLOC flag, and
* finish the writeback of the involved folios, which will be never submitted.
+ *
+ * PAGE_SET_ORDERED is still needed here:
+ * btrfs_cleanup_ordered_extents() skips @locked_page and leaves
+ * its share of the ordered extent to the caller, which can only
+ * account for it while the Ordered bit is set.
*/
if (orig_start < start) {
clear_bits = EXTENT_LOCKED | EXTENT_DELALLOC;
- page_ops = PAGE_UNLOCK | PAGE_START_WRITEBACK | PAGE_END_WRITEBACK;
+ page_ops = PAGE_UNLOCK | PAGE_SET_ORDERED |
+ PAGE_START_WRITEBACK | PAGE_END_WRITEBACK;

if (!locked_page)
mapping_set_error(inode->vfs_inode.i_mapping, ret);
--
2.39.3