FAILED: patch "[PATCH] ext4: drop extent cache after doing PARTIAL_VALID1 zeroout" failed to apply to 6.6-stable tree
Subsystems (2)
[email protected]
maintainerThe patch below does not apply to the 6.6-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-6.6.y
git checkout FETCH_HEAD
git cherry-pick -x 6d882ea3b0931b43530d44149b79fcd4ffc13030
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026022413-broken-enchanted-0ce7@gregkh' --subject-prefix 'PATCH 6.6.y' HEAD^..
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From 6d882ea3b0931b43530d44149b79fcd4ffc13030 Mon Sep 17 00:00:00 2001
From: Zhang Yi <[email protected]>
Date: Sat, 29 Nov 2025 18:32:38 +0800
Subject: [PATCH] ext4: drop extent cache after doing PARTIAL_VALID1 zeroout
When splitting an unwritten extent in the middle and converting it to initialized in ext4_split_extent() with the EXT4_EXT_MAY_ZEROOUT and EXT4_EXT_DATA_VALID2 flags set, it could leave a stale unwritten extent.
Assume we have an unwritten file and buffered write in the middle of it without dioread_nolock enabled, it will allocate blocks as written extent.
0 A B N
[UUUUUUUUUUUU] on-disk extent U: unwritten extent
[UUUUUUUUUUUU] extent status tree
[--DDDDDDDD--] D: valid data
|<- ->| ----> this range needs to be initialized
ext4_split_extent() first try to split this extent at B with EXT4_EXT_DATA_PARTIAL_VALID1 and EXT4_EXT_MAY_ZEROOUT flag set, but ext4_split_extent_at() failed to split this extent due to temporary lack of space. It zeroout B to N and leave the entire extent as unwritten.
0 A B N
[UUUUUUUUUUUU] on-disk extent
[UUUUUUUUUUUU] extent status tree
[--DDDDDDDDZZ] Z: zeroed data
ext4_split_extent() then try to split this extent at A with EXT4_EXT_DATA_VALID2 flag set. This time, it split successfully and leave an written extent from A to N.
0 A B N
[UUWWWWWWWWWW] on-disk extent W: written extent
[UUUUUUUUUUUU] extent status tree
[--DDDDDDDDZZ]
Finally ext4_map_create_blocks() only insert extent A to B to the extent status tree, and leave an stale unwritten extent in the status tree.
0 A B N
[UUWWWWWWWWWW] on-disk extent W: written extent
[UUWWWWWWWWUU] extent status tree
[--DDDDDDDDZZ]
Fix this issue by always cached extent status entry after zeroing out the second part.
Message-ID: <[email protected]>
fs/ext4/extents.c +9 -1
@@ -3319,8 +3319,16 @@ static struct ext4_ext_path *ext4_split_extent_at(handle_t *handle,* extent length and ext4_split_extent() split will the * first half again. */ -if (split_flag &EXT4_EXT_DATA_PARTIAL_VALID1 ) +if (split_flag &EXT4_EXT_DATA_PARTIAL_VALID1 ) { +/* +* Drop extent cache to prevent stale unwritten +* extents remaining after zeroing out. +*/ +ext4_es_remove_extent (inode, +le32_to_cpu (zero_ex.ee_block), +ext4_ext_get_actual_len (&zero_ex)); goto fix_extent_len; + }/* update the extent length and mark as initialized */ ex->ee_len =cpu_to_le16 (ee_len);
Sasha Levin
maintainerPATCH 1/7 ext4: get rid of ppath in ext4_find_extent()
From: Baokun Li <[email protected]>
[ Upstream commit 0be4c0c2f17bd10ae16c852f02d51a6a7b318aca ]
The use of path and ppath is now very confusing, so to make the code more readable, pass path between functions uniformly, and get rid of ppath.
Getting rid of ppath in ext4_find_extent() requires its caller to update ppath. These ppaths will also be dropped later. No functional changes.
Stable-dep-of: 6d882ea3b093 ("ext4: drop extent cache after doing PARTIAL_VALID1 zeroout")
--- fs/ext4/ext4.h | 2 +- fs/ext4/extents.c | 55 +++++++++++++++++++++++-------------------- fs/ext4/move_extent.c | 7 +++--- 3 files changed, 34 insertions(+), 30 deletions(-)
fs/ext4/ext4.h +1 -1
@@ -3712,7 +3712,7 @@ extern int ext4_ext_insert_extent(handle_t *, struct inode *, struct ext4_ext_path **, struct ext4_extent *, int);extern struct ext4_ext_path *ext4_find_extent (struct inode *,ext4_lblk_t , -struct ext4_ext_path **, +struct ext4_ext_path *,int flags);extern void ext4_free_ext_path (struct ext4_ext_path *);extern int ext4_ext_check_inode (struct inode *inode);
fs/ext4/extents.c +29 -26
164 lines not on this page · raw message
fs/ext4/move_extent.c +4 -3
@@ -26,16 +26,17 @@ static inline int get_ext_path(struct inode *inode, ext4_lblk_t lblock, struct ext4_ext_path **ppath) { - struct ext4_ext_path *path; + struct ext4_ext_path *path = *ppath; - path =ext4_find_extent (inode, lblock, ppath,EXT4_EX_NOCACHE ); + *ppath =NULL ; + path =ext4_find_extent (inode, lblock, path,EXT4_EX_NOCACHE );if (IS_ERR (path))return PTR_ERR (path);if (path[ext_depth (inode)].p_ext ==NULL ) {ext4_free_ext_path (path); - *ppath =NULL ;return -ENODATA ; } + *ppath = path;return 0 ; }
Signature · 1 line
2.51.0
Sasha Levin
maintainerPATCH 2/7 ext4: get rid of ppath in ext4_ext_create_new_leaf()
From: Baokun Li <[email protected]>
[ Upstream commit a000bc8678cc2bb10a5b80b4e991e77c7b4612fd ]
The use of path and ppath is now very confusing, so to make the code more readable, pass path between functions uniformly, and get rid of ppath.
To get rid of the ppath in ext4_ext_create_new_leaf(), the following is done here:
* Free the extents path when an error is encountered.
* Its caller needs to update ppath if it uses ppath.
No functional changes.
Stable-dep-of: 6d882ea3b093 ("ext4: drop extent cache after doing PARTIAL_VALID1 zeroout")
--- fs/ext4/extents.c | 43 ++++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 21 deletions(-)
fs/ext4/extents.c +22 -21
@@ -1392,13 +1392,12 @@ static int ext4_ext_grow_indepth(handle_t *handle, struct inode *inode,* finds empty index and adds new leaf. * if no free index is found, then it requests in-depth growing. */ -static int ext4_ext_create_new_leaf (handle_t *handle,struct inode *inode, -unsigned int mb_flags, -unsigned int gb_flags, -struct ext4_ext_path **ppath, -struct ext4_extent *newext) +static struct ext4_ext_path * +ext4_ext_create_new_leaf (handle_t *handle,struct inode *inode, +unsigned int mb_flags,unsigned int gb_flags, +struct ext4_ext_path *path, +struct ext4_extent *newext) { -struct ext4_ext_path *path = *ppath;struct ext4_ext_path *curp;int depth, i, err =0 ;
@@ -1419,28 +1418,25 @@ static int ext4_ext_create_new_leaf(handle_t *handle, struct inode *inode,* entry: create all needed subtree and add new leaf */ err =ext4_ext_split (handle, inode, mb_flags, path, newext, i);if (err) - goto out; + goto errout;/* refill path */ path =ext4_find_extent (inode, (ext4_lblk_t )le32_to_cpu (newext->ee_block), path, gb_flags); -if (IS_ERR (path)) - err =PTR_ERR (path); +return path; }else {/* tree is full, time to grow in depth */ err =ext4_ext_grow_indepth (handle, inode, mb_flags);if (err) - goto out; + goto errout;/* refill path */ path =ext4_find_extent (inode, (ext4_lblk_t )le32_to_cpu (newext->ee_block), path, gb_flags); -if (IS_ERR (path)) { - err =PTR_ERR (path); - goto out; - } +if (IS_ERR (path)) +return path;/* * only first (depth 0 -> 1) produces free space;
@@ -1452,9 +1448,11 @@ static int ext4_ext_create_new_leaf(handle_t *handle, struct inode *inode, goto repeat; } } -out: - *ppath =IS_ERR (path) ?NULL : path; -return err; +return path; + +errout: +ext4_free_ext_path (path); +return ERR_PTR (err); }/*
@@ -2097,11 +2095,14 @@ int ext4_ext_insert_extent(handle_t *handle, struct inode *inode,*/ if (gb_flags &EXT4_GET_BLOCKS_METADATA_NOFAIL ) mb_flags |=EXT4_MB_USE_RESERVED ; - err =ext4_ext_create_new_leaf (handle, inode, mb_flags, gb_flags, - ppath, newext); -if (err) + path =ext4_ext_create_new_leaf (handle, inode, mb_flags, gb_flags, + path, newext); +if (IS_ERR (path)) { + *ppath =NULL ; + err =PTR_ERR (path); goto cleanup; - path = *ppath; + } + *ppath = path; depth =ext_depth (inode); eh = path[depth].p_hdr;
Signature · 1 line
2.51.0
Sasha Levin
maintainerPATCH 3/7 ext4: get rid of ppath in ext4_ext_insert_extent()
In reply to Sasha LevinFrom: Baokun Li <[email protected]>
[ Upstream commit f7d1331f16a869c76a5102caebb58e840e1d509c ]
The use of path and ppath is now very confusing, so to make the code more readable, pass path between functions uniformly, and get rid of ppath.
To get rid of the ppath in ext4_ext_insert_extent(), the following is done here:
* Free the extents path when an error is encountered.
* Its caller needs to update ppath if it uses ppath.
* Free path when npath is used, free npath when it is not used.
* The got_allocated_blocks label in ext4_ext_map_blocks() does not update err now, so err is updated to 0 if the err returned by ext4_ext_search_right() is greater than 0 and is about to enter got_allocated_blocks.
No functional changes.
Stable-dep-of: 6d882ea3b093 ("ext4: drop extent cache after doing PARTIAL_VALID1 zeroout")
--- fs/ext4/ext4.h | 7 ++-- fs/ext4/extents.c | 88 ++++++++++++++++++++++++------------------- fs/ext4/fast_commit.c | 8 ++-- fs/ext4/migrate.c | 5 ++- 4 files changed, 61 insertions(+), 47 deletions(-)
fs/ext4/ext4.h +4 -3
@@ -3708,9 +3708,10 @@ extern int ext4_map_blocks(handle_t *handle, struct inode *inode,extern int ext4_ext_calc_credits_for_single_extent (struct inode *inode,int num,struct ext4_ext_path *path); -extern int ext4_ext_insert_extent (handle_t *,struct inode *, -struct ext4_ext_path **, -struct ext4_extent *,int ); +extern struct ext4_ext_path *ext4_ext_insert_extent ( +handle_t *handle,struct inode *inode, +struct ext4_ext_path *path, +struct ext4_extent *newext,int gb_flags);extern struct ext4_ext_path *ext4_find_extent (struct inode *,ext4_lblk_t ,struct ext4_ext_path *,int flags);
fs/ext4/extents.c +50 -38
196 lines not on this page · raw message
fs/ext4/fast_commit.c +4 -4
@@ -1806,12 +1806,12 @@ static int ext4_fc_replay_add_range(struct super_block *sb,if (ext4_ext_is_unwritten (ex))ext4_ext_mark_unwritten (&newex);down_write (&EXT4_I (inode)->i_data_sem); - ret =ext4_ext_insert_extent ( -NULL , inode, &path, &newex,0 ); + path =ext4_ext_insert_extent (NULL , inode, + path, &newex,0 );up_write ((&EXT4_I (inode)->i_data_sem)); -ext4_free_ext_path (path); -if (ret) +if (IS_ERR (path)) goto out; +ext4_free_ext_path (path); goto next; }
fs/ext4/migrate.c +3 -2
@@ -37,7 +37,6 @@ static int finish_range(handle_t *handle, struct inode *inode, path =ext4_find_extent (inode, lb->first_block,NULL ,0 );if (IS_ERR (path)) { retval =PTR_ERR (path); - path =NULL ; goto err_out; }
@@ -53,7 +52,9 @@ static int finish_range(handle_t *handle, struct inode *inode, retval =ext4_datasem_ensure_credits (handle, inode, needed, needed,0 );if (retval <0 ) goto err_out; - retval =ext4_ext_insert_extent (handle, inode, &path, &newext,0 ); + path =ext4_ext_insert_extent (handle, inode, path, &newext,0 ); +if (IS_ERR (path)) + retval =PTR_ERR (path); err_out:up_write ((&EXT4_I (inode)->i_data_sem));ext4_free_ext_path (path);
Signature · 1 line
2.51.0
Sasha Levin
maintainerPATCH 4/7 ext4: get rid of ppath in ext4_split_extent_at()
In reply to Sasha LevinFrom: Baokun Li <[email protected]>
[ Upstream commit 1de82b1b60d4613753254bf3cbf622a4c02c945c ]
The use of path and ppath is now very confusing, so to make the code more readable, pass path between functions uniformly, and get rid of ppath.
To get rid of the ppath in ext4_split_extent_at(), the following is done here:
* Free the extents path when an error is encountered.
* Its caller needs to update ppath if it uses ppath.
* Teach ext4_ext_show_leaf() to skip error pointer.
No functional changes.
Stable-dep-of: 6d882ea3b093 ("ext4: drop extent cache after doing PARTIAL_VALID1 zeroout")
--- fs/ext4/extents.c | 85 ++++++++++++++++++++++++++--------------------- 1 file changed, 47 insertions(+), 38 deletions(-)
fs/ext4/extents.c +47 -38
@@ -84,12 +84,11 @@ static void ext4_extent_block_csum_set(struct inode *inode, et->et_checksum =ext4_extent_block_csum (inode, eh); } -static int ext4_split_extent_at (handle_t *handle, -struct inode *inode, -struct ext4_ext_path **ppath, -ext4_lblk_t split, -int split_flag, -int flags); +static struct ext4_ext_path *ext4_split_extent_at (handle_t *handle, +struct inode *inode, +struct ext4_ext_path *path, +ext4_lblk_t split, +int split_flag,int flags);static int ext4_ext_trunc_restart_fn (struct inode *inode,int *dropped) {
@@ -335,9 +334,15 @@ ext4_force_split_extent_at(handle_t *handle, struct inode *inode,if (nofail) flags |=EXT4_GET_BLOCKS_METADATA_NOFAIL |EXT4_EX_NOFAIL ; -return ext4_split_extent_at (handle, inode, ppath, lblk, unwritten ? + path =ext4_split_extent_at (handle, inode, path, lblk, unwritten ?EXT4_EXT_MARK_UNWRIT1 |EXT4_EXT_MARK_UNWRIT2 :0 , flags); +if (IS_ERR (path)) { + *ppath =NULL ; +return PTR_ERR (path); + } + *ppath = path; +return 0 ; } static int
@@ -689,7 +694,7 @@ static void ext4_ext_show_leaf(struct inode *inode, struct ext4_ext_path *path)struct ext4_extent *ex;int i; -if (!path) +if (IS_ERR_OR_NULL (path))return ; eh = path[depth].p_hdr;
@@ -3153,16 +3158,14 @@ static int ext4_ext_zeroout(struct inode *inode, struct ext4_extent *ex)* a> the extent are splitted into two extent. * b> split is not needed, and just mark the extent. * -* return 0 on success. +* Return an extent path pointer on success, or an error pointer on failure. */ -static int ext4_split_extent_at (handle_t *handle, -struct inode *inode, -struct ext4_ext_path **ppath, -ext4_lblk_t split, -int split_flag, -int flags) +static struct ext4_ext_path *ext4_split_extent_at (handle_t *handle, +struct inode *inode, +struct ext4_ext_path *path, +ext4_lblk_t split, +int split_flag,int flags) { -struct ext4_ext_path *path = *ppath;ext4_fsblk_t newblock;ext4_lblk_t ee_block;struct ext4_extent *ex, newex, orig_ex, zero_ex;
@@ -3233,14 +3236,12 @@ static int ext4_split_extent_at(handle_t *handle,ext4_ext_mark_unwritten (ex2); path =ext4_ext_insert_extent (handle, inode, path, &newex, flags); -if (!IS_ERR (path)) { - *ppath = path; +if (!IS_ERR (path)) goto out; - } - *ppath =NULL ; + err =PTR_ERR (path);if (err != -ENOSPC && err != -EDQUOT && err != -ENOMEM ) -return err; +return path;/* * Get a new path to try to zeroout or fix the extent length.
@@ -3250,16 +3251,14 @@ static int ext4_split_extent_at(handle_t *handle,* in ext4_da_update_reserve_space() due to an incorrect * ee_len causing the i_reserved_data_blocks exception. */ - path =ext4_find_extent (inode, ee_block,NULL , - flags |EXT4_EX_NOFAIL ); + path =ext4_find_extent (inode, ee_block,NULL , flags |EXT4_EX_NOFAIL );if (IS_ERR (path)) {EXT4_ERROR_INODE (inode,"Failed split extent on %u, err %ld" , split,PTR_ERR (path)); -return PTR_ERR (path); +return path; } depth =ext_depth (inode); ex = path[depth].p_ext; - *ppath = path;if (EXT4_EXT_MAY_ZEROOUT & split_flag) {if (split_flag & (EXT4_EXT_DATA_VALID1 |EXT4_EXT_DATA_VALID2 )) {
@@ -3311,10 +3310,13 @@ static int ext4_split_extent_at(handle_t *handle,* and err is a non-zero error code. */ ext4_ext_dirty (handle, inode, path + path->p_depth); -return err; out: +if (err) { +ext4_free_ext_path (path); + path =ERR_PTR (err); + }ext4_ext_show_leaf (inode, path); -return err; +return path; }/*
@@ -3358,10 +3360,14 @@ static int ext4_split_extent(handle_t *handle,EXT4_EXT_MARK_UNWRIT2 ;if (split_flag &EXT4_EXT_DATA_VALID2 ) split_flag1 |=EXT4_EXT_DATA_VALID1 ; - err =ext4_split_extent_at (handle, inode, ppath, + path =ext4_split_extent_at (handle, inode, path, map->m_lblk + map->m_len, split_flag1, flags1); -if (err) +if (IS_ERR (path)) { + err =PTR_ERR (path); + *ppath =NULL ; goto out; + } + *ppath = path; }else { allocated = ee_len - (map->m_lblk - ee_block); }
@@ -3369,7 +3375,7 @@ static int ext4_split_extent(handle_t *handle,* Update path is required because previous ext4_split_extent_at() may * result in split of original leaf or extent zeroout. */ - path =ext4_find_extent (inode, map->m_lblk, *ppath, flags); + path =ext4_find_extent (inode, map->m_lblk, path, flags);if (IS_ERR (path)) { *ppath =NULL ;return PTR_ERR (path);
@@ -3391,13 +3397,17 @@ static int ext4_split_extent(handle_t *handle, split_flag1 |= split_flag & (EXT4_EXT_MAY_ZEROOUT |EXT4_EXT_MARK_UNWRIT2 ); } - err =ext4_split_extent_at (handle, inode, ppath, + path =ext4_split_extent_at (handle, inode, path, map->m_lblk, split_flag1, flags); -if (err) +if (IS_ERR (path)) { + err =PTR_ERR (path); + *ppath =NULL ; goto out; + } + *ppath = path; } -ext4_ext_show_leaf (inode, *ppath); +ext4_ext_show_leaf (inode, path); out:return err ? err : allocated; }
@@ -5583,22 +5593,21 @@ static int ext4_insert_range(struct file *file, loff_t offset, loff_t len)if (ext4_ext_is_unwritten (extent)) split_flag =EXT4_EXT_MARK_UNWRIT1 |EXT4_EXT_MARK_UNWRIT2 ; - ret =ext4_split_extent_at (handle, inode, &path, + path =ext4_split_extent_at (handle, inode, path, offset_lblk, split_flag,EXT4_EX_NOCACHE |EXT4_GET_BLOCKS_PRE_IO |EXT4_GET_BLOCKS_METADATA_NOFAIL ); } -ext4_free_ext_path (path); -if (ret <0 ) { +if (IS_ERR (path)) {up_write (&EXT4_I (inode)->i_data_sem); + ret =PTR_ERR (path); goto out_stop; } - }else { -ext4_free_ext_path (path); } +ext4_free_ext_path (path);ext4_es_remove_extent (inode, offset_lblk,EXT_MAX_BLOCKS - offset_lblk);/*
Signature · 1 line
2.51.0
Sasha Levin
maintainerPATCH 5/7 ext4: subdivide EXT4_EXT_DATA_VALID1
In reply to Sasha LevinFrom: Zhang Yi <[email protected]>
[ Upstream commit 22784ca541c0f01c5ebad14e8228298dc0a390ed ]
When splitting an extent, if the EXT4_GET_BLOCKS_CONVERT flag is set and it is necessary to split the target extent in the middle, ext4_split_extent() first handles splitting the latter half of the extent and passes the EXT4_EXT_DATA_VALID1 flag. This flag implies that all blocks before the split point contain valid data; however, this assumption is incorrect.
Therefore, subdivid EXT4_EXT_DATA_VALID1 into EXT4_EXT_DATA_ENTIRE_VALID1 and EXT4_EXT_DATA_PARTIAL_VALID1, which indicate that the first half of the extent is either entirely valid or only partially valid, respectively. These two flags cannot be set simultaneously.
This patch does not use EXT4_EXT_DATA_PARTIAL_VALID1, it only replaces EXT4_EXT_DATA_VALID1 with EXT4_EXT_DATA_ENTIRE_VALID1 at the location where it is set, no logical changes.
Message-ID: <[email protected]>
Stable-dep-of: 6d882ea3b093 ("ext4: drop extent cache after doing PARTIAL_VALID1 zeroout")
--- fs/ext4/extents.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-)
fs/ext4/extents.c +12 -6
@@ -43,8 +43,13 @@#define EXT4_EXT_MARK_UNWRIT1 0x2/* mark first half unwritten */ #define EXT4_EXT_MARK_UNWRIT2 0x4/* mark second half unwritten */ -#define EXT4_EXT_DATA_VALID1 0x8/* first half contains valid data */ -#define EXT4_EXT_DATA_VALID2 0x10/* second half contains valid data */ +/* first half contains valid data */ +#define EXT4_EXT_DATA_ENTIRE_VALID1 0x8/* has entirely valid data */ +#define EXT4_EXT_DATA_PARTIAL_VALID1 0x10/* has partially valid data */ +#define EXT4_EXT_DATA_VALID1 (EXT4_EXT_DATA_ENTIRE_VALID1 | \ + EXT4_EXT_DATA_PARTIAL_VALID1) + +#define EXT4_EXT_DATA_VALID2 0x20/* second half contains valid data */ static __le32 ext4_extent_block_csum (struct inode *inode,struct ext4_extent_header *eh)
@@ -3173,8 +3178,9 @@ static struct ext4_ext_path *ext4_split_extent_at(handle_t *handle,unsigned int ee_len, depth;int err =0 ; -BUG_ON ((split_flag & (EXT4_EXT_DATA_VALID1 |EXT4_EXT_DATA_VALID2 )) == - (EXT4_EXT_DATA_VALID1 |EXT4_EXT_DATA_VALID2 )); +BUG_ON ((split_flag &EXT4_EXT_DATA_VALID1 ) ==EXT4_EXT_DATA_VALID1 ); +BUG_ON ((split_flag &EXT4_EXT_DATA_VALID1 ) && + (split_flag &EXT4_EXT_DATA_VALID2 ));ext_debug (inode,"logical block %llu\n" , (unsigned long long )split);
@@ -3359,7 +3365,7 @@ static int ext4_split_extent(handle_t *handle, split_flag1 |=EXT4_EXT_MARK_UNWRIT1 |EXT4_EXT_MARK_UNWRIT2 ;if (split_flag &EXT4_EXT_DATA_VALID2 ) - split_flag1 |=EXT4_EXT_DATA_VALID1 ; + split_flag1 |=EXT4_EXT_DATA_ENTIRE_VALID1 ; path =ext4_split_extent_at (handle, inode, path, map->m_lblk + map->m_len, split_flag1, flags1);if (IS_ERR (path)) {
@@ -3722,7 +3728,7 @@ static int ext4_split_convert_extents(handle_t *handle,/* Convert to unwritten */ if (flags &EXT4_GET_BLOCKS_CONVERT_UNWRITTEN ) { - split_flag |=EXT4_EXT_DATA_VALID1 ; + split_flag |=EXT4_EXT_DATA_ENTIRE_VALID1 ;/* Convert to initialized */ }else if (flags &EXT4_GET_BLOCKS_CONVERT ) { split_flag |= ee_block + ee_len <= eof_block ?
Signature · 1 line
2.51.0
Sasha Levin
maintainerPATCH 6/7 ext4: don't zero the entire extent if EXT4_EXT_DATA_PARTIAL_VALID1
In reply to Sasha LevinFrom: Zhang Yi <[email protected]>
[ Upstream commit 1bf6974822d1dba86cf11b5f05498581cf3488a2 ]
When allocating initialized blocks from a large unwritten extent, or when splitting an unwritten extent during end I/O and converting it to initialized, there is currently a potential issue of stale data if the extent needs to be split in the middle.
0 A B N
[UUUUUUUUUUUU] U: unwritten extent
[--DDDDDDDD--] D: valid data
|<- ->| ----> this range needs to be initialized
ext4_split_extent() first try to split this extent at B with EXT4_EXT_DATA_ENTIRE_VALID1 and EXT4_EXT_MAY_ZEROOUT flag set, but ext4_split_extent_at() failed to split this extent due to temporary lack of space. It zeroout B to N and mark the entire extent from 0 to N as written.
0 A B N
[WWWWWWWWWWWW] W: written extent
[SSDDDDDDDDZZ] Z: zeroed, S: stale data
ext4_split_extent() then try to split this extent at A with EXT4_EXT_DATA_VALID2 flag set. This time, it split successfully and left a stale written extent from 0 to A.
0 A B N
[WW|WWWWWWWWWW]
[SS|DDDDDDDDZZ]Fix this by pass EXT4_EXT_DATA_PARTIAL_VALID1 to ext4_split_extent_at() when splitting at B, don't convert the entire extent to written and left it as unwritten after zeroing out B to N. The remaining work is just like the standard two-part split. ext4_split_extent() will pass the EXT4_EXT_DATA_VALID2 flag when it calls ext4_split_extent_at() for the second time, allowing it to properly handle the split. If the split is successful, it will keep extent from 0 to A as unwritten.
Message-ID: <[email protected]>
Stable-dep-of: 6d882ea3b093 ("ext4: drop extent cache after doing PARTIAL_VALID1 zeroout")
--- fs/ext4/extents.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-)
fs/ext4/extents.c +12 -1
@@ -3293,6 +3293,15 @@ static struct ext4_ext_path *ext4_split_extent_at(handle_t *handle, }if (!err) { +/* +* The first half contains partially valid data, the +* splitting of this extent has not been completed, fix +* extent length and ext4_split_extent() split will the +* first half again. +*/ +if (split_flag &EXT4_EXT_DATA_PARTIAL_VALID1 ) + goto fix_extent_len; +/* update the extent length and mark as initialized */ ex->ee_len =cpu_to_le16 (ee_len);ext4_ext_try_to_merge (handle, inode, path, ex);
@@ -3365,7 +3374,9 @@ static int ext4_split_extent(handle_t *handle, split_flag1 |=EXT4_EXT_MARK_UNWRIT1 |EXT4_EXT_MARK_UNWRIT2 ;if (split_flag &EXT4_EXT_DATA_VALID2 ) - split_flag1 |=EXT4_EXT_DATA_ENTIRE_VALID1 ; + split_flag1 |= map->m_lblk > ee_block ? +EXT4_EXT_DATA_PARTIAL_VALID1 : +EXT4_EXT_DATA_ENTIRE_VALID1 ; path =ext4_split_extent_at (handle, inode, path, map->m_lblk + map->m_len, split_flag1, flags1);if (IS_ERR (path)) {
Signature · 1 line
2.51.0
Sasha Levin
maintainerPATCH 7/7 ext4: drop extent cache after doing PARTIAL_VALID1 zeroout
In reply to Sasha LevinFrom: Zhang Yi <[email protected]>
[ Upstream commit 6d882ea3b0931b43530d44149b79fcd4ffc13030 ]
When splitting an unwritten extent in the middle and converting it to initialized in ext4_split_extent() with the EXT4_EXT_MAY_ZEROOUT and EXT4_EXT_DATA_VALID2 flags set, it could leave a stale unwritten extent.
Assume we have an unwritten file and buffered write in the middle of it without dioread_nolock enabled, it will allocate blocks as written extent.
0 A B N
[UUUUUUUUUUUU] on-disk extent U: unwritten extent
[UUUUUUUUUUUU] extent status tree
[--DDDDDDDD--] D: valid data
|<- ->| ----> this range needs to be initialized
ext4_split_extent() first try to split this extent at B with EXT4_EXT_DATA_PARTIAL_VALID1 and EXT4_EXT_MAY_ZEROOUT flag set, but ext4_split_extent_at() failed to split this extent due to temporary lack of space. It zeroout B to N and leave the entire extent as unwritten.
0 A B N
[UUUUUUUUUUUU] on-disk extent
[UUUUUUUUUUUU] extent status tree
[--DDDDDDDDZZ] Z: zeroed data
ext4_split_extent() then try to split this extent at A with EXT4_EXT_DATA_VALID2 flag set. This time, it split successfully and leave an written extent from A to N.
0 A B N
[UUWWWWWWWWWW] on-disk extent W: written extent
[UUUUUUUUUUUU] extent status tree
[--DDDDDDDDZZ]
Finally ext4_map_create_blocks() only insert extent A to B to the extent status tree, and leave an stale unwritten extent in the status tree.
0 A B N
[UUWWWWWWWWWW] on-disk extent W: written extent
[UUWWWWWWWWUU] extent status tree
[--DDDDDDDDZZ]
Fix this issue by always cached extent status entry after zeroing out the second part.
Message-ID: <[email protected]>
--- fs/ext4/extents.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-)
fs/ext4/extents.c +9 -1
@@ -3299,8 +3299,16 @@ static struct ext4_ext_path *ext4_split_extent_at(handle_t *handle,* extent length and ext4_split_extent() split will the * first half again. */ -if (split_flag &EXT4_EXT_DATA_PARTIAL_VALID1 ) +if (split_flag &EXT4_EXT_DATA_PARTIAL_VALID1 ) { +/* +* Drop extent cache to prevent stale unwritten +* extents remaining after zeroing out. +*/ +ext4_es_remove_extent (inode, +le32_to_cpu (zero_ex.ee_block), +ext4_ext_get_actual_len (&zero_ex)); goto fix_extent_len; + }/* update the extent length and mark as initialized */ ex->ee_len =cpu_to_le16 (ee_len);
Signature · 1 line
2.51.0