[PATCH -next v5 19/32] ext4: tolerate unexpected holes in ext4_convert_unwritten_extents()
Zhang Yi <[email protected]>
| Newsgroups | org.kernel.vger.linux-fsdevel,org.kernel.vger.linux-ext4,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
From: Zhang Yi <[email protected]> Because the iomap infrastructure does not always create an ifs to manage sub-folio state when folio size is larger than blocksize, invalidating a partial dirty folio during punch hole may fail to clear the dirty state of the affected range. As a result, writeback of that folio may observe a hole. At writeback submit time, ext4_map_blocks() already handles this case and will not allocate blocks. However, when punch hole races with writeback, the following scenario can cause I/O completion to encounter a hole. punch hole writeback ---------- --------- ext4_punch_hole() ext4_truncate_page_cache_block_range() iomap_invalidate_folio() [partial folio] iomap_clear_range_dirty() -- no ifs, sub-block dirty bits NOT cleared ext4_iomap_writepages() iomap_writepages() ext4_iomap_writeback_submit() ext4_iomap_map_writeback_range() ext4_map_blocks(IO_SUBMIT) -> extent exists, not a hole submit_io() -> bio in flight down_write(&i_data_sem) ext4_es_remove_extent() ext4_ext_remove_space() -> extent removed, hole inserted up_write(&i_data_sem) [bio completes] ext4_iomap_finish_ioend() ext4_convert_unwritten_extents() ext4_map_blocks(IO_CONVERT_EXT) -> returns 0 (hole found) Therefore, in ext4_convert_unwritten_extents() we need to be careful about the case where ext4_map_blocks() returns 0. Instead of triggering a warning, we should ignore the hole and continue with the subsequent conversion. Link: https://lore.kernel.org/linux-ext4/[email protected]/ Signed-off-by: Zhang Yi <[email protected]> --- fs/ext4/extents.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c index 0d62d9312284..5a06259a9b5d 100644 --- a/fs/ext4/extents.c +++ b/fs/ext4/extents.c @@ -5166,19 +5166,21 @@ int ext4_convert_unwritten_extents(handle_t *handle, struct inode *inode, ret = ext4_map_blocks(handle, inode, &map, EXT4_GET_BLOCKS_IO_CONVERT_EXT | EXT4_EX_NOCACHE); - if (ret <= 0) { - /* - * If the ret is zero, an unexpected hole may cause - * conversion to fail. To avoid data loss during I/O - * end conversion, skip the hole and continue - * converting subsequent blocks. - */ + /* + * A return value of zero means an unexpected hole was found. + * This can happen when writeback races with a concurrent + * punch hole in the iomap path. Because iomap may not create + * ifs for folios larger than block size, the dirty bit can + * be set again after punching. If writeback happens between + * partial folio invalidation and extent removal, a hole is + * observed at I/O completion. + */ + if (ret < 0) ext4_warning(inode->i_sb, "inode #%llu: block %u: len %u: ext4_map_blocks returned %d", inode->i_ino, map.m_lblk, map.m_len, ret); - } else { + else if (ret > 0) conv_blocks += map.m_len; - } ret2 = ext4_mark_inode_dirty(handle, inode); if (credits) { -- 2.52.0