[PATCH v2 0/6] ext4: fix unaligned edge handling in FALLOC_FL_WRITE_ZEROES

From: Zhang Yi

Date: Mon Jul 06 2026 - 22:22:52 EST


Hi,

This is v2 of the series to fix unaligned edge handling in
FALLOC_FL_WRITE_ZEROES. This version drops the last cleanup patch in v1
and adds a new patch protecting the WRITE_ZEROES crash window reported
by Jan[1]. The two pre-existing fixes around ext4_block_zero_eof() and
ext4_load_tail_bh()[2] that reported by Sashiko will be sent separately.

Changes since v1:
- Pick up Jan's Reviewed-by on patches 1, 3, and 5.
- Expand patch 2 to also explain why a clean unwritten buffer returns
NULL (a lookup-only get_block never sets BH_Mapped for unwritten
extents), per Jan's review.
- Drop the last cleanup patch "skip ext4_update_disksize_before_punch()
in WRITE_ZEROES" because it's wrong.
- Add a new patch to protect the WRITE_ZEROES crash window by adding
the inode to the orphan list in the same handle that does the
unwritten-to-written conversion, and removing it once i_disksize
has caught up.

v1: https://lore.kernel.org/linux-xfs/0b7f1a4f-da1c-4297-8099-98d738070ab7@xxxxxxxxxxxxxxx/

Origin cover-letter
===================

The current FALLOC_FL_WRITE_ZEROES implementation in ext4 does not
correctly handle unaligned edge blocks. FALLOC_FL_WRITE_ZEROES is needed
to convert the requested range to written extents with zeroed content,
but the existing code only partially zero the edges and never guarantees
that:

1) edges that are clean unwritten extents or holes get promoted to
written extents, and
2) edges that are dirty unwritten or delalloc get their underlying
extents converted to written before the syscall returns.

Both cases leave the on-disk extent type unwritten, violating the
WRITE_ZEROES contract, and in these cases a subsequent sync buffered
overwrite would still observe pending metadata changes from the
conversion work that should have been completed by WRITE_ZEROES itself.

This series fixes the unaligned edge handling and cleans up a related
redundant i_disksize update. The first three patches are preparatory:
moving ext4_zero_partial_blocks() earlier in ext4_zero_range(),
documenting the return semantics of ext4_load_tail_bh(), and replacing
the single bool output of ext4_zero_partial_blocks() with a per-edge
bitmask (EXT4_PARTIAL_ZERO_START/END). The next two patches fix the two
scenarios above by expanding the aligned allocation range outward for
skipped (clean unwritten or hole) edges, and by writing back
partial-zeroed edges so the underlying extents are converted to written.
The final patch drops the now-redundant
ext4_update_disksize_before_punch() call on the WRITE_ZEROES path, since
WRITE_ZEROES is mutually exclusive with KEEP_SIZE and the i_disksize
update is already handled by ext4_alloc_file_blocks().

Thanks,
Yi.

[1] https://lore.kernel.org/linux-ext4/3f6ao5amv7glbgigndtegcucgo3n34ij3lau6l3da3hgdxgn3v@ev66wv3r5umt/
[2] https://sashiko.dev/#/patchset/20260701142009.1510104-1-yizhang089%40gmail.com?part=2

Discussion Link:
https://lore.kernel.org/linux-ext4/3f6ao5amv7glbgigndtegcucgo3n34ij3lau6l3da3hgdxgn3v@ev66wv3r5umt/


Zhang Yi (6):
ext4: move partial block zeroing earlier in ext4_zero_range()
ext4: clarify return semantics of ext4_load_tail_bh()
ext4: track partial-zero outcome per edge in
ext4_zero_partial_blocks()
ext4: zero out whole block for clean edges in WRITE_ZEROES
ext4: write back partial-zeroed edges in WRITE_ZEROES
ext4: protect WRITE_ZEROES written extents with orphan list

fs/ext4/ext4.h | 5 +++-
fs/ext4/extents.c | 71 +++++++++++++++++++++++++++++++++++++++--------
fs/ext4/inode.c | 47 ++++++++++++++++++++++++++-----
3 files changed, 104 insertions(+), 19 deletions(-)

--
2.52.0