[RFC v3 07/11] ext4: add ext4_map_blocks_extsize() wrapper to handle overwrites

From: Ojaswin Mujoo
Date: Mon Mar 24 2025 - 03:41:02 EST


Currently, with the extsize hints, if we consider a scenario where
the hint is is set to 16k and we do a write of (0,4k) we get the below
mapping:

[ 4k written ] [ 12k unwritten ]

Now, if we do a (4k,4k) write, ext4_map_blocks will again try for a
extsize aligned write, adjust the range to (0, 16k) and then run into
issues since the new range is already has a mapping in it. Although this
does not lead to a failure since we eventually fallback to a non extsize
allocation, this is not a good approach.

Hence, implement a wrapper over ext4_map_blocks() which detects if a
mapping already exists for an extsize based allocation and then reuses
the same mapping.

In case the mapping completely covers the original request we simply
disable extsize allocation and call map_blocks to correctly process the
mapping and set the map flags. Otherwise, if there is a hole or partial
mapping, then we just let ext4_map_blocks() handle the allocation.

Signed-off-by: Ojaswin Mujoo <ojaswin@xxxxxxxxxxxxx>
---
fs/ext4/inode.c | 49 ++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 46 insertions(+), 3 deletions(-)

diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index bf19b9f99cea..e41c97584f35 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -678,6 +678,42 @@ static inline void ext4_extsize_reset_map(struct ext4_map_blocks *map,
map->m_flags = 0;
}

+static int ext4_map_blocks_extsize(handle_t *handle, struct inode *inode,
+ struct ext4_map_blocks *map, int flags)
+{
+ int orig_mlen = map->m_len;
+ int ret = 0;
+ int tmp_flags;
+
+ WARN_ON(!ext4_inode_get_extsize(EXT4_I(inode)));
+ WARN_ON(!(flags | EXT4_GET_BLOCKS_CREATE_UNWRIT_EXT));
+
+ /*
+ * First check if there are any existing allocations
+ */
+ ret = ext4_map_blocks(handle, inode, map, 0);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * the present mapping fully covers the requested range. In this
+ * case just go for a non extsize based allocation. Note that we won't
+ * really be allocating new blocks but the call to ext4_map_blocks is
+ * important to ensure things like extent splitting and proper map flags
+ * are taken care of. For all other cases, just let ext4_map_blocks handle
+ * the allocations
+ */
+ if (ret > 0 && map->m_len == orig_mlen)
+ tmp_flags = flags & ~(EXT4_GET_BLOCKS_EXTSIZE |
+ EXT4_GET_BLOCKS_FORCEALIGN);
+ else
+ tmp_flags = flags;
+
+ ret = ext4_map_blocks(handle, inode, map, tmp_flags);
+
+ return ret;
+}
+
/*
* The ext4_map_blocks() function tries to look up the requested blocks,
* and returns if the blocks are already mapped.
@@ -1028,8 +1064,12 @@ static int _ext4_get_block(struct inode *inode, sector_t iblock,
map.m_lblk = iblock;
map.m_len = orig_mlen;

- ret = ext4_map_blocks(ext4_journal_current_handle(), inode, &map,
- flags);
+ if ((flags & EXT4_GET_BLOCKS_CREATE) && ext4_should_use_extsize(inode))
+ ret = ext4_map_blocks_extsize(ext4_journal_current_handle(), inode,
+ &map, flags);
+ else
+ ret = ext4_map_blocks(ext4_journal_current_handle(), inode,
+ &map, flags);
if (ret > 0) {
map_bh(bh, inode->i_sb, map.m_pblk);
ext4_update_bh_state(bh, map.m_flags);
@@ -3647,7 +3687,10 @@ static int ext4_iomap_alloc(struct inode *inode, struct ext4_map_blocks *map,
m_flags |= EXT4_GET_BLOCKS_EXTSIZE;
}

- ret = ext4_map_blocks(handle, inode, map, m_flags);
+ if (ext4_should_use_extsize(inode))
+ ret = ext4_map_blocks_extsize(handle, inode, map, m_flags);
+ else
+ ret = ext4_map_blocks(handle, inode, map, m_flags);

/*
* We cannot fill holes in indirect tree based inodes as that could
--
2.48.1