Commit 8c19d54b authored by Zhihao Cheng's avatar Zhihao Cheng
Browse files

ext4: Fix wrong da count caused by concurrent racing on extent tree

Offering: HULK
hulk inclusion
category: bugfix
bugzilla: https://gitee.com/openeuler/kernel/issues/IAODWM



--------------------------------

The writeback process and error branch in writeback process may
concurrently access extent tree, which leads to wrong da reservation
count is calculated, specifically:

write(4M) // folio 0, 1 are dirty

   wb_workfn              fsync
ext4_iomap_writepages
 iomap_do_writepage // writeback folio 0
  iomap_writepage_map_blocks
   ext4_iomap_map_blocks
    ext4_iomap_map_one_extent
     ext4_map_create_blocks  // down write(EXT4_I(inode)->i_data_sem)
      ext4_ext_map_blocks
       ext4_da_update_reserve_space // da reservation becomes 0
      .
      .  >> EIO occurs, journal is aborted <<
      .              ext4_sync_file
      .               file_write_and_wait_range
      .                do_writepages // writeback folio 1
      .                 ext4_iomap_writepages
      .                  iomap_writepage_map_blocks
      .                   map_blocks // failed
      .                   discard_folio // error branch
      .                    ext4_es_remove_extent
      .                    // scan extents, delayed es is 512
      .                     ext4_da_release_space
      .                     // da reservation count, 0 < 512
      .                      WARN_ON(1)
      ext4_es_insert_extent  // convert delayed es to unwritten status

Since all changes on extent tree are protected by holding
EXT4_I(inode)->i_data_sem, fix the problem by adding the lock
while doing ext4_es_remove_extent().

Fixes: 7f6416dc ("ext4: implement writeback iomap path")
Signed-off-by: default avatarZhihao Cheng <chengzhihao1@huawei.com>
parent 1f0e4758
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -3676,8 +3676,10 @@ static inline int ext4_iomap_buffered_da_write_begin(struct inode *inode,
static int ext4_iomap_punch_delalloc(struct inode *inode, loff_t offset,
				     loff_t length)
{
	down_write(&EXT4_I(inode)->i_data_sem);
	ext4_es_remove_extent(inode, offset >> inode->i_blkbits,
			DIV_ROUND_UP_ULL(length, EXT4_BLOCK_SIZE(inode->i_sb)));
	up_write(&EXT4_I(inode)->i_data_sem);
	return 0;
}