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 Closes: https://lore.kernel.org/linux-ext4/3f6ao5amv7glbgigndtegcucgo3n34ij3lau6l3da3hgdxgn3v@ev66wv3r5umt/ Fixes: f4265b8d32c4 ("ext4: add FALLOC_FL_WRITE_ZEROES support") Cc: stable@vger.kernel.org Signed-off-by: Zhang Yi --- 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