[PATCH v2] ext4: record writeback error when discarding pages on failed delalloc

From: guolingxing

Date: Fri Oct 16 2026 - 07:06:23 EST


When mpage_map_and_submit_extent() gives up on a failed block mapping,
it sets give_up_on_write so that mpage_release_unused_pages() discards
the remaining dirty folios. However, the error is not recorded in the
mapping's writeback error state.

If this happens during background writeback, a later fsync() may
report success even though the dirty data was discarded and never
written. Returning the error from ext4_writepages() is not sufficient
to notify a later fsync(). In addition, ext4_do_writepages() may clear
-ENOSPC when committing the journal and retrying, even after the
affected dirty folios have been discarded.

Call mapping_set_error() when deciding to give up on the write,
before the dirty folios are invalidated, so that fsync() can report
the writeback failure through the mapping's error state.

The give_up_on_write flag also persists across writeback loop
iterations. After a fatal -ENOSPC failure sets it, ext4_do_writepages()
may commit the journal and retry without clearing the flag. If a
subsequent iteration encounters a transient error and leaves dirty
folios pending, mpage_release_unused_pages() invalidates them instead
of preserving them for retry. Initialize give_up_on_write inside the
loop so that only a fatal failure in the current iteration causes
the remaining dirty folios to be discarded.

Fixes: c4a0c46ec92c ("ext4: invalidate pages if delalloc block allocation fails.")
Fixes: cb530541182b ("ext4: fix up error handling for mpage_map_and_submit_extent()")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: guolingxing <guolingxing@xxxxxxxxxx>
---
Changes since v1:
- Initialize give_up_on_write in each writeback iteration to prevent
a stale value from invalidating dirty folios after an ENOSPC retry.
- Update the commit message and add the corresponding Fixes tag.

fs/ext4/inode.c | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index 26f0f9714f03..ae54212e0a7d 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -2505,6 +2505,12 @@ static int mpage_map_and_submit_extent(handle_t *handle,
ext4_print_free_blocks(inode);
invalidate_dirty_pages:
*give_up_on_write = true;
+ /*
+ * Record the error so that a subsequent fsync()
+ * / errseq check can observe the data loss that
+ * discarding these dirty pages implies.
+ */
+ mapping_set_error(inode->i_mapping, err);
return err;
}
progress = 1;
@@ -2774,7 +2780,6 @@ static int ext4_do_writepages(struct mpage_da_data *mpd)
int needed_blocks, rsv_blocks = 0, ret = 0;
struct ext4_sb_info *sbi = EXT4_SB(mapping->host->i_sb);
struct blk_plug plug;
- bool give_up_on_write = false;

trace_ext4_writepages(inode, wbc);

@@ -2892,6 +2897,8 @@ static int ext4_do_writepages(struct mpage_da_data *mpd)
goto unplug;

while (!mpd->scanned_until_end && wbc->nr_to_write > 0) {
+ bool give_up_on_write = false;
+
/* For each extent of pages we use new io_end */
mpd->io_submit.io_end = ext4_init_io_end(inode, GFP_KERNEL);
if (!mpd->io_submit.io_end) {
--
2.34.1