[PATCH AUTOSEL 6.19-6.12] ext4: propagate flags to convert_initialized_extent()

Sasha Levin
2026-02-14T21:27:03Z

From: Ojaswin Mujoo <ojaswin@linux.ibm.com>

[ Upstream commit 3fffa44b6ebf65be92a562a5063303979385a1c9 ]

Currently, ext4_zero_range passes EXT4_EX_NOCACHE flag to avoid caching
extents however this is not respected by convert_initialized_extent().
Hence, modify it to accept flags from the caller and to pass the flags
on to other extent manipulation functions it calls. This makes
sure the NOCACHE flag is respected throughout the code path.

Also, we no longer explicitly pass CONVERT_UNWRITTEN as the caller takes
care of this.

Reviewed-by: Zhang Yi <yi.zhang@huawei.com>
Reviewed-by: Jan Kara <jack@suse.cz>
Signed-off-by: Ojaswin Mujoo <ojaswin@linux.ibm.com>
Link: https://patch.msgid.link/07008fbb14db727fddcaf4c30e2346c49f6c8fe0.1769149131.git.ojaswin@linux.ibm.com
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---

LLM Generated explanations, may be completely bogus:

### 4. Deeper Analysis: Is This Just Performance or Correctness?

The investigation reveals important nuances:

**EXT4_EX_NOCACHE** prevents populating the extent status (ES) tree
cache during operations like `ext4_zero_range`. The purpose is stated in
the code: "we shouldn't be caching the extents when reading from the
extent tree while a truncate or punch hole operation is in progress."

The reason is **correctness, not just performance**: During operations
that modify the extent tree (like zero_range, which splits/converts
extents), caching intermediate extent states can leave **stale entries**
in the ES tree. Recent fixes (commits `6d882ea3b0931` and
`c2b3575dfcc7f` from Nov 2025, both Cc: stable) explicitly address data
corruption from stale extent cache entries during these exact
operations.

**However**, the specific code path in this commit:

1. `ext4_find_extent()` with flags=0 instead of flags with NOCACHE -
this means extents get cached when they shouldn't be during
`convert_initialized_extent()`
2. `ext4_split_convert_extents()` was getting hardcoded
`EXT4_GET_BLOCKS_CONVERT_UNWRITTEN` instead of the caller's flags
(which include both `CONVERT_UNWRITTEN` and `NOCACHE`)

### 5. Risk and Scope Assessment

**Changes are small and surgical:**
- Only 1 file changed (fs/ext4/extents.c)
- Function signature change: adding `int flags` parameter
- 3 call sites modified: passing `flags` instead of hardcoded values
- Net code change is minimal

**Risk of regression:** LOW
- The flags being propagated are the same flags the caller already has
- The hardcoded `EXT4_GET_BLOCKS_CONVERT_UNWRITTEN` was already included
in the `flags` from the caller (the call site check `flags &
EXT4_GET_BLOCKS_CONVERT_UNWRITTEN` confirms this)
- The main behavioral change is that `EXT4_EX_NOCACHE` now reaches these
inner functions

**Subsystem impact:** HIGH - ext4 is the most widely used Linux
filesystem

### 6. Does It Fix a Real Bug?

Yes. While the commit message is understated, the actual bug is:
- `ext4_zero_range` deliberately sets `EXT4_EX_NOCACHE` to prevent stale
extent cache entries during extent tree modifications
- But `convert_initialized_extent()` was dropping this flag, leading to
extents being cached during the modification operation
- This can cause stale extent cache entries, which related commits have
proven can lead to **data corruption**

The commit message says "makes sure the NOCACHE flag is respected
throughout the code path" - this is a correctness fix for extent cache
consistency.

### 7. Dependency Check

The commit is self-contained. It modifies a static function and its
single call site within the same file. No external dependencies.

### 8. Summary

| Criterion | Assessment |
|-----------|-----------|
| Fixes real bug | YES - stale extent cache during zero_range can cause
corruption |
| Obviously correct | YES - propagating caller flags is the right
pattern |
| Small and contained | YES - minimal changes to one file |
| Risk of regression | LOW - only adds flag propagation that was already
intended |
| Subsystem importance | HIGH - ext4 filesystem |
| User impact | MEDIUM-HIGH - affects anyone using fallocate/zero_range
on ext4 |

This commit fixes a real correctness issue in ext4's extent cache
management during `ext4_zero_range` operations. The NOCACHE flag was
being dropped, which could lead to stale extent cache entries - a class
of bug that has been directly linked to data corruption in related
recent fixes. The change is small, surgical, obviously correct, and
carries minimal regression risk.

**YES**

fs/ext4/extents.c | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
index f1322f64071ff..4e97d1e7213f3 100644
--- a/fs/ext4/extents.c
+++ b/fs/ext4/extents.c
@@ -3818,6 +3818,7 @@ static struct ext4_ext_path *
convert_initialized_extent(handle_t *handle, struct inode *inode,
struct ext4_map_blocks *map,
struct ext4_ext_path *path,
+ int flags,
unsigned int *allocated)
{
struct ext4_extent *ex;
@@ -3843,11 +3844,11 @@ convert_initialized_extent(handle_t *handle, struct inode *inode,

if (ee_block != map->m_lblk || ee_len > map->m_len) {
path = ext4_split_convert_extents(handle, inode, map, path,
- EXT4_GET_BLOCKS_CONVERT_UNWRITTEN, NULL);
+ flags, NULL);
if (IS_ERR(path))
return path;

- path = ext4_find_extent(inode, map->m_lblk, path, 0);
+ path = ext4_find_extent(inode, map->m_lblk, path, flags);
if (IS_ERR(path))
return path;
depth = ext_depth(inode);
@@ -4259,7 +4260,7 @@ int ext4_ext_map_blocks(handle_t *handle, struct inode *inode,
if ((!ext4_ext_is_unwritten(ex)) &&
(flags & EXT4_GET_BLOCKS_CONVERT_UNWRITTEN)) {
path = convert_initialized_extent(handle,
- inode, map, path, &allocated);
+ inode, map, path, flags, &allocated);
if (IS_ERR(path))
err = PTR_ERR(path);
goto out;
--
2.51.0