Re: [PATCH v2 6/6] ext4: protect WRITE_ZEROES written extents with orphan list
From: Jan Kara
Date: Wed Jul 08 2026 - 12:10:24 EST
On Tue 07-07-26 10:13:38, Zhang Yi wrote:
> In ext4_alloc_file_blocks(), the WRITE_ZEROES path converts unwritten
> extents to written in one transaction while i_disksize is updated to
> cover them only in a later transaction. A crash in between leaves
> written extents beyond i_disksize on disk, which fsck will complain
> about this.
>
> Add the inode to the orphan list in the same handle that does the
> conversion, and remove it once i_disksize has caught up. Fold
> ext4_convert_unwritten_extents() into the same handle so the orphan
> add and the conversion are atomic. Also add a check before conversion
> to ensure the conversion does not extend beyond the end of the file.
>
> Reported-by: Jan Kara <jack@xxxxxxx>
> Closes: https://lore.kernel.org/linux-ext4/3f6ao5amv7glbgigndtegcucgo3n34ij3lau6l3da3hgdxgn3v@ev66wv3r5umt/
> Fixes: f4265b8d32c4 ("ext4: add FALLOC_FL_WRITE_ZEROES support")
> Cc: stable@xxxxxxxxxxxxxxx
> Signed-off-by: Zhang Yi <yi.zhang@xxxxxxxxxx>
Looks good. Feel free to add:
Reviewed-by: Jan Kara <jack@xxxxxxx>
Honza
> ---
> fs/ext4/extents.c | 32 ++++++++++++++++++++++++++++----
> 1 file changed, 28 insertions(+), 4 deletions(-)
>
> diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
> index c083703bf704..8c7a98405a64 100644
> --- a/fs/ext4/extents.c
> +++ b/fs/ext4/extents.c
> @@ -4585,6 +4585,7 @@ static int ext4_alloc_file_blocks(struct file *file, loff_t offset, loff_t len,
> loff_t epos = 0, old_size = i_size_read(inode);
> unsigned int blkbits = inode->i_blkbits;
> bool alloc_zero = false;
> + bool orphan = false;
>
> BUG_ON(!ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS));
> map.m_lblk = offset >> blkbits;
> @@ -4659,12 +4660,30 @@ static int ext4_alloc_file_blocks(struct file *file, loff_t offset, loff_t len,
>
> if (alloc_zero &&
> (map.m_flags & (EXT4_MAP_MAPPED | EXT4_MAP_UNWRITTEN))) {
> + WARN_ON_ONCE(map.m_lblk + map.m_len >
> + EXT4_B_TO_LBLK(inode, new_size ?: old_size));
> +
> ret = ext4_issue_zeroout(inode, map.m_lblk, map.m_pblk,
> map.m_len);
> - if (likely(!ret))
> - ret = ext4_convert_unwritten_extents(NULL,
> + if (unlikely(ret))
> + break;
> +
> + handle = ext4_journal_start(inode, EXT4_HT_MAP_BLOCKS,
> + credits);
> + if (IS_ERR(handle)) {
> + ret = PTR_ERR(handle);
> + break;
> + }
> +
> + ret = ext4_convert_unwritten_extents(handle,
> inode, (loff_t)map.m_lblk << blkbits,
> (loff_t)map.m_len << blkbits);
> + if (!ret && new_size) {
> + ret = ext4_orphan_add(handle, inode);
> + orphan = true;
> + }
> + ret2 = ext4_journal_stop(handle);
> + ret = ret ? ret : ret2;
> if (ret)
> break;
> }
> @@ -4687,12 +4706,17 @@ static int ext4_alloc_file_blocks(struct file *file, loff_t offset, loff_t len,
> if (epos > new_size)
> epos = new_size;
>
> - handle = ext4_journal_start(inode, EXT4_HT_MISC, 1);
> - if (IS_ERR(handle))
> + handle = ext4_journal_start(inode, EXT4_HT_MISC, 2);
> + if (IS_ERR(handle)) {
> + if (orphan && inode->i_nlink)
> + ext4_orphan_del(NULL, inode);
> return ret ? ret : PTR_ERR(handle);
> + }
>
> ext4_update_inode_size(inode, epos);
> ret2 = ext4_mark_inode_dirty(handle, inode);
> + if (orphan && inode->i_nlink)
> + ext4_orphan_del(handle, inode);
> ext4_update_inode_fsync_trans(handle, inode, 1);
> ret3 = ext4_journal_stop(handle);
> ret2 = ret3 ? ret3 : ret2;
> --
> 2.52.0
>
--
Jan Kara <jack@xxxxxxxx>
SUSE Labs, CR