summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--fs/ext4/inode.c18
1 files changed, 18 insertions, 0 deletions
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index 309c656247e0..86d1e027e543 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -734,6 +734,7 @@ int ext4_map_blocks(handle_t *handle, struct inode *inode,
else
ext4_check_map_extents_env(inode);
+create_retry:
/* Lookup extent status tree firstly */
if (ext4_es_lookup_extent(inode, map->m_lblk, NULL, &es, &map->m_seq)) {
if (ext4_es_is_written(&es) || ext4_es_is_unwritten(&es)) {
@@ -784,6 +785,8 @@ int ext4_map_blocks(handle_t *handle, struct inode *inode,
down_read(&EXT4_I(inode)->i_data_sem);
retval = ext4_map_query_blocks(handle, inode, map, flags);
up_read((&EXT4_I(inode)->i_data_sem));
+ if (retval < 0)
+ return retval;
found:
if (retval > 0 && map->m_flags & EXT4_MAP_MAPPED) {
@@ -820,6 +823,21 @@ found:
* with create == 1 flag.
*/
down_write(&EXT4_I(inode)->i_data_sem);
+
+ /*
+ * Check the validity of the mapping found via the extent status
+ * tree or the disk query. A racing truncate may have changed the
+ * extent, since writeback may not hold i_rwsem or the folio locks
+ * covering the full extent(e.g., the iomap writeback path may
+ * allocate an extent that extends beyond the length of the locked
+ * folio in advance).
+ */
+ if (map->m_seq != READ_ONCE(EXT4_I(inode)->i_es_seq)) {
+ up_write(&EXT4_I(inode)->i_data_sem);
+ map->m_flags = 0;
+ map->m_len = orig_mlen;
+ goto create_retry;
+ }
retval = ext4_map_create_blocks(handle, inode, map, flags);
up_write((&EXT4_I(inode)->i_data_sem));