From: Qu Wenruo wqu@suse.com
[ Upstream commit 4bcd3061e8154606af7f721cb75ca04ffe191a12 ]
[BUG] If submit_one_sector() failed, the block will be kept dirty, but with their corresponding range finished in the ordered extent.
This means if a writeback happens later again, we can hit the following problems:
- ASSERT(block_start != EXTENT_MAP_HOLE) in submit_one_sector() If the original extent map is a hole, then we can hit this case, as the new ordered extent failed, we will drop the new extent map and re-read one from the disk.
- DEBUG_WARN() in btrfs_writepage_cow_fixup() This is because we no longer have an ordered extent for those dirty blocks. The original for them is already finished with error.
[CAUSE] The function submit_one_sector() is not following the regular error handling of writeback. The common practice is to clear the folio dirty, start and finish the writeback for the block.
This is normally done by extent_clear_unlock_delalloc() with PAGE_START_WRITEBACK | PAGE_END_WRITEBACK flags during run_delalloc_range().
So if we keep those failed blocks dirty, they will stay in the page cache and wait for the next writeback.
And since the original ordered extent is already finished and removed, depending on the original extent map, we either hit the ASSERT() inside submit_one_sector(), or hit the DEBUG_WARN() in btrfs_writepage_cow_fixup().
[FIX] Follow the regular error handling to clear the dirty flag for the block, start and finish writeback for that block instead.
Reviewed-by: Filipe Manana fdmanana@suse.com Signed-off-by: Qu Wenruo wqu@suse.com Signed-off-by: David Sterba dsterba@suse.com Signed-off-by: Sasha Levin sashal@kernel.org ---
LLM Generated explanations, may be completely bogus:
Based on my analysis of the commit and the kernel repository context, here's my assessment:
**Backport Status: YES**
This commit should be backported to stable kernel trees for the following reasons:
## Bug Fix Analysis
1. **Critical Bug Being Fixed**: The commit fixes a serious error handling bug in btrfs writeback that can lead to two different assertion failures: - `ASSERT(block_start != EXTENT_MAP_HOLE)` in submit_one_sector() - `DEBUG_WARN()` in btrfs_writepage_cow_fixup()
2. **Data Integrity Issue**: The bug causes dirty blocks to remain dirty after a failed submission, but their corresponding ordered extent is already finished with error. This creates an inconsistent state where: - Dirty blocks exist without proper ordered extent tracking - Subsequent writeback attempts will fail with assertions/warnings - The filesystem enters an undefined state that could affect data integrity
3. **Clear Root Cause**: The commit message clearly identifies the problem - submit_one_sector() was not following standard writeback error handling practices. The fix aligns the error handling with the rest of the btrfs writeback code.
## Code Change Analysis
The fix is minimal and contained: ```c if (IS_ERR(em)) { + /* + * When submission failed, we should still clear the folio dirty. + * Or the folio will be written back again but without any + * ordered extent. + */ + btrfs_folio_clear_dirty(fs_info, folio, filepos, sectorsize); + btrfs_folio_set_writeback(fs_info, folio, filepos, sectorsize); + btrfs_folio_clear_writeback(fs_info, folio, filepos, sectorsize); return PTR_ERR(em); } ```
The changes: - Add proper error handling to clear dirty flag - Set and clear writeback status to properly finish the failed writeback - Update comments to clarify the behavior
## Stable Tree Criteria Met
1. **Fixes a real bug**: Yes - prevents assertion failures and potential filesystem corruption 2. **Small and contained**: Yes - only ~10 lines of actual code change in one function 3. **No new features**: Correct - purely bug fix 4. **Low regression risk**: The change follows established patterns used elsewhere in btrfs (extent_clear_unlock_delalloc) 5. **Important enough**: Yes - prevents filesystem errors and potential data integrity issues
The fix is straightforward, follows existing btrfs patterns, and addresses a clear bug that could affect users running btrfs filesystems, especially under I/O error conditions. This makes it an excellent candidate for stable backporting.
fs/btrfs/extent_io.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-)
diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c index 1dc931c4937f..2e127c109e5b 100644 --- a/fs/btrfs/extent_io.c +++ b/fs/btrfs/extent_io.c @@ -1483,7 +1483,7 @@ static noinline_for_stack int writepage_delalloc(struct btrfs_inode *inode,
/* * Return 0 if we have submitted or queued the sector for submission. - * Return <0 for critical errors. + * Return <0 for critical errors, and the sector will have its dirty flag cleared. * * Caller should make sure filepos < i_size and handle filepos >= i_size case. */ @@ -1506,8 +1506,17 @@ static int submit_one_sector(struct btrfs_inode *inode, ASSERT(filepos < i_size);
em = btrfs_get_extent(inode, NULL, filepos, sectorsize); - if (IS_ERR(em)) + if (IS_ERR(em)) { + /* + * When submission failed, we should still clear the folio dirty. + * Or the folio will be written back again but without any + * ordered extent. + */ + btrfs_folio_clear_dirty(fs_info, folio, filepos, sectorsize); + btrfs_folio_set_writeback(fs_info, folio, filepos, sectorsize); + btrfs_folio_clear_writeback(fs_info, folio, filepos, sectorsize); return PTR_ERR(em); + }
extent_offset = filepos - em->start; em_end = btrfs_extent_map_end(em); @@ -1637,8 +1646,8 @@ static noinline_for_stack int extent_writepage_io(struct btrfs_inode *inode, * Here we set writeback and clear for the range. If the full folio * is no longer dirty then we clear the PAGECACHE_TAG_DIRTY tag. * - * If we hit any error, the corresponding sector will still be dirty - * thus no need to clear PAGECACHE_TAG_DIRTY. + * If we hit any error, the corresponding sector will have its dirty + * flag cleared and writeback finished, thus no need to handle the error case. */ if (!submitted_io && !error) { btrfs_folio_set_writeback(fs_info, folio, start, len);