From nobody Sat Feb 7 21:53:15 2026 Received: from mx0b-001b2d01.pphosted.com (mx0b-001b2d01.pphosted.com [148.163.158.5]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 32120372B2B; Fri, 23 Jan 2026 06:26:04 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=148.163.158.5 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769149571; cv=none; b=kJdJuAumJh370abHUYwLFWUW5eY6waFxTusZYIlNOPAOxv4l9Jg7tUtyvo2JtgaVqJp2IddDmigQ+rR7/xt2njc9Sj6RvkAHMtkFbDLf1VKtKTj+Nf+SR9qZ9ACTVcGlzUHSkSVQJBqJvVfjEA2vQ5N+bA/jf8VOzeQ+ypwHsKk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769149571; c=relaxed/simple; bh=1OCvyJFcfGf4F80MZCh11VsAkqTDLjw/2cwZJpZsTrM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=LNfMc3a+Plzper4TL8ChYMMSxFcIoythfAfCqMgxesBBJDtMd43cOXswljvtM/Uzb1qv+6GTD3gIpAEDE7rn1bb7l7zlvLjW/kPApH+vrQIF36fKQdnBIcmKDMui7DWR84KaFJslzi5vvMM8tfC+/jPtD2rYKJyUknNYmY/vI9I= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.ibm.com; spf=pass smtp.mailfrom=linux.ibm.com; dkim=pass (2048-bit key) header.d=ibm.com header.i=@ibm.com header.b=RayHvLBC; arc=none smtp.client-ip=148.163.158.5 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.ibm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.ibm.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=ibm.com header.i=@ibm.com header.b="RayHvLBC" Received: from pps.filterd (m0353725.ppops.net [127.0.0.1]) by mx0a-001b2d01.pphosted.com (8.18.1.2/8.18.1.2) with ESMTP id 60MIHdJQ005941; Fri, 23 Jan 2026 06:25:48 GMT DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=ibm.com; h=cc :content-transfer-encoding:date:from:in-reply-to:message-id :mime-version:references:subject:to; s=pp1; bh=cGOo9GbL+Hw0tFROu LSC/D9tWOMq6qVuZl8QitzOePQ=; b=RayHvLBCyMnDcVWfaONRafzU35K5FE0q6 2bLwWcvexNk+PGk888IaqI3Okf08cwR5sRnJN1GDSzRmSUYsByfxQK6tft1kfsSP X4mZxhW6Bk+hc2ubJU+ewnwGasbsGtvtXkgRvnyvevUB9i0JyFwrrTJDbGAdVuEp DG7u3Rozczshqr1OWKu7WANZ8fGwT17lv1CO8gLN8Czch/XIDzLsJMtPaZ1zPyIZ s/hde6Sf/4/oWUMs7wZfTW3vRO8mjN9PvLP2XpzVDX2LIgUvr5jlWLMdjQAkEnAl c3xGfnMstRB4EKJwO1/tdwrl2HgO9kt89VFEUTrC6cna32VPk3+1A== Received: from pps.reinject (localhost [127.0.0.1]) by mx0a-001b2d01.pphosted.com (PPS) with ESMTPS id 4bus1ptpym-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Fri, 23 Jan 2026 06:25:47 +0000 (GMT) Received: from m0353725.ppops.net (m0353725.ppops.net [127.0.0.1]) by pps.reinject (8.18.1.12/8.18.0.8) with ESMTP id 60N6PlcZ018495; Fri, 23 Jan 2026 06:25:47 GMT Received: from ppma13.dal12v.mail.ibm.com (dd.9e.1632.ip4.static.sl-reverse.com [50.22.158.221]) by mx0a-001b2d01.pphosted.com (PPS) with ESMTPS id 4bus1ptpyg-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Fri, 23 Jan 2026 06:25:47 +0000 (GMT) Received: from pps.filterd (ppma13.dal12v.mail.ibm.com [127.0.0.1]) by ppma13.dal12v.mail.ibm.com (8.18.1.2/8.18.1.2) with ESMTP id 60N4fcpa001162; Fri, 23 Jan 2026 06:25:46 GMT Received: from smtprelay06.fra02v.mail.ibm.com ([9.218.2.230]) by ppma13.dal12v.mail.ibm.com (PPS) with ESMTPS id 4brpyk7a39-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Fri, 23 Jan 2026 06:25:46 +0000 Received: from smtpav02.fra02v.mail.ibm.com (smtpav02.fra02v.mail.ibm.com [10.20.54.101]) by smtprelay06.fra02v.mail.ibm.com (8.14.9/8.14.9/NCO v10.0) with ESMTP id 60N6Pioj30212482 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK); Fri, 23 Jan 2026 06:25:44 GMT Received: from smtpav02.fra02v.mail.ibm.com (unknown [127.0.0.1]) by IMSVA (Postfix) with ESMTP id 82C9B2004F; Fri, 23 Jan 2026 06:25:44 +0000 (GMT) Received: from smtpav02.fra02v.mail.ibm.com (unknown [127.0.0.1]) by IMSVA (Postfix) with ESMTP id 5D92A20043; Fri, 23 Jan 2026 06:25:42 +0000 (GMT) Received: from li-dc0c254c-257c-11b2-a85c-98b6c1322444.ibm.com (unknown [9.39.26.206]) by smtpav02.fra02v.mail.ibm.com (Postfix) with ESMTP; Fri, 23 Jan 2026 06:25:42 +0000 (GMT) From: Ojaswin Mujoo To: linux-ext4@vger.kernel.org, "Theodore Ts'o" Cc: Ritesh Harjani , Zhang Yi , Jan Kara , libaokun1@huawei.com, linux-kernel@vger.kernel.org Subject: [PATCH v4 1/8] ext4: kunit tests for extent splitting and conversion Date: Fri, 23 Jan 2026 11:55:32 +0530 Message-ID: <22bb9d17cd88c1318a2edde48887ca7488cb8a13.1769149131.git.ojaswin@linux.ibm.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-TM-AS-GCONF: 00 X-Proofpoint-ORIG-GUID: n6v2Cq2BjCQTFnTanNOJLe9wVhsKIr4x X-Proofpoint-Spam-Details-Enc: AW1haW4tMjYwMTIzMDA0NiBTYWx0ZWRfX6SFRmN6/nvLk Il5hPoAm0Xf0LJf0D7ziTxi2L2sOjovmEYzsrFFrkzg/Vc3ekWIKkrBVaYHXiiZdY0HFMNIKLAD JXKWnH2AewmuEFdBpsjXuD4eg408PM+FdMXbO2MzXhWxt7/74b6+FcjVoo/NfJwvoDl8tAZTBZE Y76b/7BQUwsFolXNjaw7w/wMNbOsXS7Rw+ITqXbMbWPDZ+ztznNIUMk/mnbEH+QuOxOERLi9yT+ 3PqwzPkG3gn8+vRzSsAkbdyxXW/YZyrTVfANINkOLW8QtyfjIKvJkuq9LGoczRF5E0363lvckiA 7cg6k0yFclE3vivzxJPTPMQ5ea4yhJcr/9GN4YkgeLsUD+RtrcA+kw2jFwR8v8mt5tyzhZ4A3bQ bur9Z1BapkDKCwZvcXY0LNnME4jiCxMPEQMc9eQuoEruSZqRPvcBdXmWDiLB/ILKy8kVsZv2ymy Zs2RhAwDUygKd0XJUDg== X-Proofpoint-GUID: _0wVEQ-IeObNMquNG858K0KWEjFf2wc2 X-Authority-Analysis: v=2.4 cv=GY8aXAXL c=1 sm=1 tr=0 ts=6973146b cx=c_pps a=AfN7/Ok6k8XGzOShvHwTGQ==:117 a=AfN7/Ok6k8XGzOShvHwTGQ==:17 a=vUbySO9Y5rIA:10 a=VkNPw1HP01LnGYTKEx00:22 a=VwQbUJbxAAAA:8 a=VnNF1IyMAAAA:8 a=i0EeH86SAAAA:8 a=OOxyGJNtmdNL3i7zqkUA:9 X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1121,Hydra:6.1.20,FMLib:17.12.100.49 definitions=2026-01-22_06,2026-01-22_02,2025-10-01_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 suspectscore=0 impostorscore=0 phishscore=0 lowpriorityscore=0 spamscore=0 malwarescore=0 priorityscore=1501 bulkscore=0 adultscore=0 clxscore=1015 classifier=typeunknown authscore=0 authtc= authcc= route=outbound adjust=0 reason=mlx scancount=1 engine=8.19.0-2601150000 definitions=main-2601230046 Content-Type: text/plain; charset="utf-8" Add multiple KUnit tests to test various permutations of extent splitting and conversion. We test the following cases: 1. Split of unwritten extent into 2 parts and convert 1 part to written 2. Split of unwritten extent into 3 parts and convert 1 part to written 3. Split of written extent into 2 parts and convert 1 part to unwritten 4. Split of written extent into 3 parts and convert 1 part to unwritten 5. Zeroout fallback for all the above cases except 3-4 because zeroout is not supported for written to unwritten splits The main function we test here is ext4_split_convert_extents(). Currently some of the tests are failing due to issues in implementation. All failures are mitigated at other layers in ext4 [1] but still point out the mismatch in expectation of what the caller wants vs what the function does. The aim is to eventually fix all the failures we see here. More detailed implementation notes can be found in the topmost commit in the test file. [1] for example, EXT4_GET_BLOCKS_CONVERT doesn't really convert the split extent to written, but rather the callers end up doing the conversion. Reviewed-by: Zhang Yi Reviewed-by: Jan Kara Signed-off-by: Ojaswin Mujoo --- fs/ext4/extents-test.c | 559 +++++++++++++++++++++++++++++++++++++++ fs/ext4/extents.c | 23 +- fs/ext4/extents_status.c | 3 + fs/ext4/inode.c | 4 + 4 files changed, 587 insertions(+), 2 deletions(-) create mode 100644 fs/ext4/extents-test.c diff --git a/fs/ext4/extents-test.c b/fs/ext4/extents-test.c new file mode 100644 index 000000000000..15053c607cfd --- /dev/null +++ b/fs/ext4/extents-test.c @@ -0,0 +1,559 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Written by Ojaswin Mujoo (IBM) + * + * These Kunit tests are designed to test the functionality of + * extent split and conversion in ext4. + * + * Currently, ext4 can split extents in 2 ways: + * 1. By splitting the extents in the extent tree and optionally convertin= g them + * to written or unwritten based on flags passed. + * 2. In case 1 encounters an error, ext4 instead zerooes out the unwritten + * areas of the extent and marks the complete extent written. + * + * The primary function that handles this is ext4_split_convert_extents(). + * + * We test both of the methods of split. The behavior we try to enforce is: + * 1. When passing EXT4_GET_BLOCKS_CONVERT flag to ext4_split_convert_exte= nts(), + * the split extent should be converted to initialized. + * 2. When passing EXT4_GET_BLOCKS_CONVERT_UNWRITTEN flag to + * ext4_split_convert_extents(), the split extent should be converted to + * uninitialized. + * 3. In case we use the zeroout method, then we should correctly write ze= roes + * to the unwritten areas of the extent and we should not corrupt/leak = any + * data. + * + * Enforcing 1 and 2 is straight forward, we just setup a minimal inode wi= th + * extent tree, call ext4_split_convert_extents() and check the final stat= e of + * the extent tree. + * + * For zeroout testing, we maintain a separate buffer which represents the= disk + * data corresponding to the extents. We then override ext4's zeroout func= tions + * to instead write zeroes to our buffer. Then, we override + * ext4_ext_insert_extent() to return -ENOSPC, which triggers the zeroout. + * Finally, we check the state of the extent tree and zeroout buffer to co= nfirm + * everything went well. + */ + +#include +#include +#include +#include + +#include "ext4.h" +#include "ext4_extents.h" + +#define EXT_DATA_PBLK 100 +#define EXT_DATA_LBLK 10 +#define EXT_DATA_LEN 3 + +struct kunit_ctx { + /* + * Ext4 inode which has only 1 unwrit extent + */ + struct ext4_inode_info *k_ei; + /* + * Represents the underlying data area (used for zeroout testing) + */ + char *k_data; +} k_ctx; + +/* + * describes the state of an expected extent in extent tree. + */ +struct kunit_ext_state { + ext4_lblk_t ex_lblk; + ext4_lblk_t ex_len; + bool is_unwrit; +}; + +/* + * describes the state of the data area of a writ extent. Used for testing + * correctness of zeroout. + */ +struct kunit_ext_data_state { + char exp_char; + ext4_lblk_t off_blk; + ext4_lblk_t len_blk; +}; + +struct kunit_ext_test_param { + /* description of test */ + char *desc; + + /* is extent unwrit at beginning of test */ + bool is_unwrit_at_start; + + /* flags to pass while splitting */ + int split_flags; + + /* map describing range to split */ + struct ext4_map_blocks split_map; + + /* no of extents expected after split */ + int nr_exp_ext; + + /* + * expected state of extents after split. We will never split into more + * than 3 extents + */ + struct kunit_ext_state exp_ext_state[3]; + + /* Below fields used for zeroout tests */ + + bool is_zeroout_test; + /* + * no of expected data segments (zeroout tests). Example, if we expect + * data to be 4kb 0s, followed by 8kb non-zero, then nr_exp_data_segs=3D= =3D2 + */ + int nr_exp_data_segs; + + /* + * expected state of data area after zeroout. + */ + struct kunit_ext_data_state exp_data_state[3]; +}; + +static void ext_kill_sb(struct super_block *sb) +{ + generic_shutdown_super(sb); +} + +static int ext_set(struct super_block *sb, void *data) +{ + return 0; +} + +static struct file_system_type ext_fs_type =3D { + .name =3D "extents test", + .kill_sb =3D ext_kill_sb, +}; + +static void extents_kunit_exit(struct kunit *test) +{ + kfree(k_ctx.k_ei); + kfree(k_ctx.k_data); +} + +static void ext4_cache_extents_stub(struct inode *inode, + struct ext4_extent_header *eh) +{ + return; +} + +static int __ext4_ext_dirty_stub(const char *where, unsigned int line, + handle_t *handle, struct inode *inode, + struct ext4_ext_path *path) +{ + return 0; +} + +static struct ext4_ext_path * +ext4_ext_insert_extent_stub(handle_t *handle, struct inode *inode, + struct ext4_ext_path *path, + struct ext4_extent *newext, int gb_flags) +{ + return ERR_PTR(-ENOSPC); +} + +static void ext4_es_remove_extent_stub(struct inode *inode, ext4_lblk_t lb= lk, + ext4_lblk_t len) +{ + return; +} + +static void ext4_zeroout_es_stub(struct inode *inode, struct ext4_extent *= ex) +{ + return; +} + +/* + * We will zeroout the equivalent range in the data area + */ +static int ext4_ext_zeroout_stub(struct inode *inode, struct ext4_extent *= ex) +{ + ext4_lblk_t ee_block, off_blk; + loff_t ee_len; + loff_t off_bytes; + struct kunit *test =3D kunit_get_current_test(); + + ee_block =3D le32_to_cpu(ex->ee_block); + ee_len =3D ext4_ext_get_actual_len(ex); + + KUNIT_EXPECT_EQ_MSG(test, 1, ee_block >=3D EXT_DATA_LBLK, "ee_block=3D%d", + ee_block); + KUNIT_EXPECT_EQ(test, 1, + ee_block + ee_len <=3D EXT_DATA_LBLK + EXT_DATA_LEN); + + off_blk =3D ee_block - EXT_DATA_LBLK; + off_bytes =3D off_blk << inode->i_sb->s_blocksize_bits; + memset(k_ctx.k_data + off_bytes, 0, + ee_len << inode->i_sb->s_blocksize_bits); + + return 0; +} + +static int ext4_issue_zeroout_stub(struct inode *inode, ext4_lblk_t lblk, + ext4_fsblk_t pblk, ext4_lblk_t len) +{ + ext4_lblk_t off_blk; + loff_t off_bytes; + struct kunit *test =3D kunit_get_current_test(); + + kunit_log(KERN_ALERT, test, + "%s: lblk=3D%u pblk=3D%llu len=3D%u", __func__, lblk, pblk, len); + KUNIT_EXPECT_EQ(test, 1, lblk >=3D EXT_DATA_LBLK); + KUNIT_EXPECT_EQ(test, 1, lblk + len <=3D EXT_DATA_LBLK + EXT_DATA_LEN); + KUNIT_EXPECT_EQ(test, 1, lblk - EXT_DATA_LBLK =3D=3D pblk - EXT_DATA_PBLK= ); + + off_blk =3D lblk - EXT_DATA_LBLK; + off_bytes =3D off_blk << inode->i_sb->s_blocksize_bits; + memset(k_ctx.k_data + off_bytes, 0, + len << inode->i_sb->s_blocksize_bits); + + return 0; +} + +static int extents_kunit_init(struct kunit *test) +{ + struct ext4_extent_header *eh =3D NULL; + struct ext4_inode_info *ei; + struct inode *inode; + struct super_block *sb; + struct kunit_ext_test_param *param =3D + (struct kunit_ext_test_param *)(test->param_value); + + /* setup the mock inode */ + k_ctx.k_ei =3D kzalloc(sizeof(struct ext4_inode_info), GFP_KERNEL); + if (k_ctx.k_ei =3D=3D NULL) + return -ENOMEM; + ei =3D k_ctx.k_ei; + inode =3D &ei->vfs_inode; + + sb =3D sget(&ext_fs_type, NULL, ext_set, 0, NULL); + if (IS_ERR(sb)) + return PTR_ERR(sb); + + sb->s_blocksize =3D 4096; + sb->s_blocksize_bits =3D 12; + + ei->i_disksize =3D (EXT_DATA_LBLK + EXT_DATA_LEN + 10) << sb->s_blocksize= _bits; + inode->i_sb =3D sb; + + k_ctx.k_data =3D kzalloc(EXT_DATA_LEN * 4096, GFP_KERNEL); + if (k_ctx.k_data =3D=3D NULL) + return -ENOMEM; + + /* + * set the data area to a junk value + */ + memset(k_ctx.k_data, 'X', EXT_DATA_LEN * 4096); + + /* create a tree with depth 0 */ + eh =3D (struct ext4_extent_header *)k_ctx.k_ei->i_data; + + /* Fill extent header */ + eh =3D ext_inode_hdr(&k_ctx.k_ei->vfs_inode); + eh->eh_depth =3D 0; + eh->eh_entries =3D cpu_to_le16(1); + eh->eh_magic =3D EXT4_EXT_MAGIC; + eh->eh_max =3D + cpu_to_le16(ext4_ext_space_root_idx(&k_ctx.k_ei->vfs_inode, 0)); + eh->eh_generation =3D 0; + + /* + * add 1 extent in leaf node covering lblks [10,13) and pblk [100,103) + */ + EXT_FIRST_EXTENT(eh)->ee_block =3D cpu_to_le32(EXT_DATA_LBLK); + EXT_FIRST_EXTENT(eh)->ee_len =3D cpu_to_le16(EXT_DATA_LEN); + ext4_ext_store_pblock(EXT_FIRST_EXTENT(eh), EXT_DATA_PBLK); + if (!param || param->is_unwrit_at_start) + ext4_ext_mark_unwritten(EXT_FIRST_EXTENT(eh)); + + /* Add stubs */ + kunit_activate_static_stub(test, ext4_cache_extents, + ext4_cache_extents_stub); + kunit_activate_static_stub(test, __ext4_ext_dirty, + __ext4_ext_dirty_stub); + kunit_activate_static_stub(test, ext4_es_remove_extent, + ext4_es_remove_extent_stub); + kunit_activate_static_stub(test, ext4_zeroout_es, ext4_zeroout_es_stub); + kunit_activate_static_stub(test, ext4_ext_zeroout, ext4_ext_zeroout_stub); + kunit_activate_static_stub(test, ext4_issue_zeroout, + ext4_issue_zeroout_stub); + return 0; +} + +/* + * Return 1 if all bytes in the buf equal to c, else return the offset of = first mismatch + */ +static int check_buffer(char *buf, int c, int size) +{ + void *ret =3D NULL; + + ret =3D memchr_inv(buf, c, size); + if (ret =3D=3D NULL) + return 0; + + kunit_log(KERN_ALERT, kunit_get_current_test(), + "# %s: wrong char found at offset %u (expected:%d got:%d)", __func__, + (u32)((char *)ret - buf), c, *((char *)ret)); + return 1; +} + +static void test_split_convert(struct kunit *test) +{ + struct ext4_ext_path *path; + struct inode *inode =3D &k_ctx.k_ei->vfs_inode; + struct ext4_extent *ex; + struct ext4_map_blocks map; + const struct kunit_ext_test_param *param =3D + (const struct kunit_ext_test_param *)(test->param_value); + int blkbits =3D inode->i_sb->s_blocksize_bits; + + if (param->is_zeroout_test) + /* + * Force zeroout by making ext4_ext_insert_extent return ENOSPC + */ + kunit_activate_static_stub(test, ext4_ext_insert_extent, + ext4_ext_insert_extent_stub); + + path =3D ext4_find_extent(inode, EXT_DATA_LBLK, NULL, 0); + ex =3D path->p_ext; + KUNIT_EXPECT_EQ(test, EXT_DATA_LBLK, le32_to_cpu(ex->ee_block)); + KUNIT_EXPECT_EQ(test, EXT_DATA_LEN, ext4_ext_get_actual_len(ex)); + KUNIT_EXPECT_EQ(test, param->is_unwrit_at_start, + ext4_ext_is_unwritten(ex)); + if (param->is_zeroout_test) + KUNIT_EXPECT_EQ(test, 0, + check_buffer(k_ctx.k_data, 'X', + EXT_DATA_LEN << blkbits)); + + map.m_lblk =3D param->split_map.m_lblk; + map.m_len =3D param->split_map.m_len; + ext4_split_convert_extents(NULL, inode, &map, path, + param->split_flags, NULL); + + path =3D ext4_find_extent(inode, EXT_DATA_LBLK, NULL, 0); + ex =3D path->p_ext; + + for (int i =3D 0; i < param->nr_exp_ext; i++) { + struct kunit_ext_state exp_ext =3D param->exp_ext_state[i]; + + KUNIT_EXPECT_EQ(test, exp_ext.ex_lblk, + le32_to_cpu(ex->ee_block)); + KUNIT_EXPECT_EQ(test, exp_ext.ex_len, + ext4_ext_get_actual_len(ex)); + KUNIT_EXPECT_EQ(test, exp_ext.is_unwrit, + ext4_ext_is_unwritten(ex)); + + /* Only printed on failure */ + kunit_log(KERN_INFO, test, + "# [extent %d] exp: lblk:%d len:%d unwrit:%d \n", i, + exp_ext.ex_lblk, exp_ext.ex_len, exp_ext.is_unwrit); + kunit_log(KERN_INFO, test, + "# [extent %d] got: lblk:%d len:%d unwrit:%d\n", i, + le32_to_cpu(ex->ee_block), + ext4_ext_get_actual_len(ex), + ext4_ext_is_unwritten(ex)); + kunit_log(KERN_INFO, test, "------------------\n"); + + ex =3D ex + 1; + } + + if (!param->is_zeroout_test) + return; + + /* + * Check that then data area has been zeroed out correctly + */ + for (int i =3D 0; i < param->nr_exp_data_segs; i++) { + loff_t off, len; + struct kunit_ext_data_state exp_data_seg =3D param->exp_data_state[i]; + + off =3D exp_data_seg.off_blk << blkbits; + len =3D exp_data_seg.len_blk << blkbits; + KUNIT_EXPECT_EQ_MSG(test, 0, + check_buffer(k_ctx.k_data + off, + exp_data_seg.exp_char, len), + "# corruption in byte range [%lld, %lld)", + off, len); + } + + return; +} + +static const struct kunit_ext_test_param test_split_convert_params[] =3D { + /* unwrit to writ splits */ + { .desc =3D "split unwrit extent to 2 extents and convert 1st half writ", + .is_unwrit_at_start =3D 1, + .split_flags =3D EXT4_GET_BLOCKS_CONVERT, + .split_map =3D { .m_lblk =3D EXT_DATA_LBLK, .m_len =3D 1 }, + .nr_exp_ext =3D 2, + .exp_ext_state =3D { { .ex_lblk =3D EXT_DATA_LBLK, + .ex_len =3D 1, + .is_unwrit =3D 0 }, + { .ex_lblk =3D EXT_DATA_LBLK + 1, + .ex_len =3D EXT_DATA_LEN - 1, + .is_unwrit =3D 1 } }, + .is_zeroout_test =3D 0 }, + { .desc =3D "split unwrit extent to 2 extents and convert 2nd half writ", + .is_unwrit_at_start =3D 1, + .split_flags =3D EXT4_GET_BLOCKS_CONVERT, + .split_map =3D { .m_lblk =3D EXT_DATA_LBLK + 1, .m_len =3D EXT_DATA_LEN= - 1 }, + .nr_exp_ext =3D 2, + .exp_ext_state =3D { { .ex_lblk =3D EXT_DATA_LBLK, + .ex_len =3D 1, + .is_unwrit =3D 1 }, + { .ex_lblk =3D EXT_DATA_LBLK + 1, + .ex_len =3D EXT_DATA_LEN - 1, + .is_unwrit =3D 0 } }, + .is_zeroout_test =3D 0 }, + { .desc =3D "split unwrit extent to 3 extents and convert 2nd half to wri= t", + .is_unwrit_at_start =3D 1, + .split_flags =3D EXT4_GET_BLOCKS_CONVERT, + .split_map =3D { .m_lblk =3D EXT_DATA_LBLK + 1, .m_len =3D EXT_DATA_LEN= - 2 }, + .nr_exp_ext =3D 3, + .exp_ext_state =3D { { .ex_lblk =3D EXT_DATA_LBLK, + .ex_len =3D 1, + .is_unwrit =3D 1 }, + { .ex_lblk =3D EXT_DATA_LBLK + 1, + .ex_len =3D EXT_DATA_LEN - 2, + .is_unwrit =3D 0 }, + { .ex_lblk =3D EXT_DATA_LBLK + 1 + (EXT_DATA_LEN - 2), + .ex_len =3D 1, + .is_unwrit =3D 1 } }, + .is_zeroout_test =3D 0 }, + + /* writ to unwrit splits */ + { .desc =3D "split writ extent to 2 extents and convert 1st half unwrit", + .is_unwrit_at_start =3D 0, + .split_flags =3D EXT4_GET_BLOCKS_CONVERT_UNWRITTEN, + .split_map =3D { .m_lblk =3D EXT_DATA_LBLK, .m_len =3D 1 }, + .nr_exp_ext =3D 2, + .exp_ext_state =3D { { .ex_lblk =3D EXT_DATA_LBLK, + .ex_len =3D 1, + .is_unwrit =3D 1 }, + { .ex_lblk =3D EXT_DATA_LBLK + 1, + .ex_len =3D EXT_DATA_LEN - 1, + .is_unwrit =3D 0 } }, + .is_zeroout_test =3D 0 }, + { .desc =3D "split writ extent to 2 extents and convert 2nd half unwrit", + .is_unwrit_at_start =3D 0, + .split_flags =3D EXT4_GET_BLOCKS_CONVERT_UNWRITTEN, + .split_map =3D { .m_lblk =3D EXT_DATA_LBLK + 1, .m_len =3D EXT_DATA_LEN= - 1 }, + .nr_exp_ext =3D 2, + .exp_ext_state =3D { { .ex_lblk =3D EXT_DATA_LBLK, + .ex_len =3D 1, + .is_unwrit =3D 0 }, + { .ex_lblk =3D EXT_DATA_LBLK + 1, + .ex_len =3D EXT_DATA_LEN - 1, + .is_unwrit =3D 1 } }, + .is_zeroout_test =3D 0 }, + { .desc =3D "split writ extent to 3 extents and convert 2nd half to unwri= t", + .is_unwrit_at_start =3D 0, + .split_flags =3D EXT4_GET_BLOCKS_CONVERT_UNWRITTEN, + .split_map =3D { .m_lblk =3D EXT_DATA_LBLK + 1, .m_len =3D EXT_DATA_LEN= - 2 }, + .nr_exp_ext =3D 3, + .exp_ext_state =3D { { .ex_lblk =3D EXT_DATA_LBLK, + .ex_len =3D 1, + .is_unwrit =3D 0 }, + { .ex_lblk =3D EXT_DATA_LBLK + 1, + .ex_len =3D EXT_DATA_LEN - 2, + .is_unwrit =3D 1 }, + { .ex_lblk =3D EXT_DATA_LBLK + 1 + (EXT_DATA_LEN - 2), + .ex_len =3D 1, + .is_unwrit =3D 0 } }, + .is_zeroout_test =3D 0 }, + + /* + * ***** zeroout tests ***** + */ + /* unwrit to writ splits */ + { .desc =3D "split unwrit extent to 2 extents and convert 1st half writ (= zeroout)", + .is_unwrit_at_start =3D 1, + .split_flags =3D EXT4_GET_BLOCKS_CONVERT, + .split_map =3D { .m_lblk =3D EXT_DATA_LBLK, .m_len =3D 1 }, + .nr_exp_ext =3D 1, + .exp_ext_state =3D { { .ex_lblk =3D EXT_DATA_LBLK, + .ex_len =3D EXT_DATA_LEN, + .is_unwrit =3D 0 } }, + .is_zeroout_test =3D 1, + .nr_exp_data_segs =3D 2, + .exp_data_state =3D { { .exp_char =3D 'X', .off_blk =3D 0, .len_blk =3D= 1 }, + { .exp_char =3D 0, + .off_blk =3D 1, + .len_blk =3D EXT_DATA_LEN - 1 } } }, + { .desc =3D "split unwrit extent to 2 extents and convert 2nd half writ (= zeroout)", + .is_unwrit_at_start =3D 1, + .split_flags =3D EXT4_GET_BLOCKS_CONVERT, + .split_map =3D { .m_lblk =3D EXT_DATA_LBLK + 1, .m_len =3D EXT_DATA_LEN= - 1 }, + .nr_exp_ext =3D 1, + .exp_ext_state =3D { { .ex_lblk =3D EXT_DATA_LBLK, + .ex_len =3D EXT_DATA_LEN, + .is_unwrit =3D 0 } }, + .is_zeroout_test =3D 1, + .nr_exp_data_segs =3D 2, + .exp_data_state =3D { { .exp_char =3D 0, .off_blk =3D 0, .len_blk =3D 1= }, + { .exp_char =3D 'X', + .off_blk =3D 1, + .len_blk =3D EXT_DATA_LEN - 1 } } }, + { .desc =3D "split unwrit extent to 3 extents and convert 2nd half writ (= zeroout)", + .is_unwrit_at_start =3D 1, + .split_flags =3D EXT4_GET_BLOCKS_CONVERT, + .split_map =3D { .m_lblk =3D EXT_DATA_LBLK + 1, .m_len =3D EXT_DATA_LEN= - 2 }, + .nr_exp_ext =3D 1, + .exp_ext_state =3D { { .ex_lblk =3D EXT_DATA_LBLK, + .ex_len =3D EXT_DATA_LEN, + .is_unwrit =3D 0 } }, + .is_zeroout_test =3D 1, + .nr_exp_data_segs =3D 3, + .exp_data_state =3D { { .exp_char =3D 0, .off_blk =3D 0, .len_blk =3D 1= }, + { .exp_char =3D 'X', + .off_blk =3D 1, + .len_blk =3D EXT_DATA_LEN - 2 }, + { .exp_char =3D 0, + .off_blk =3D EXT_DATA_LEN - 1, + .len_blk =3D 1 } } }, +}; + +static void ext_get_desc(struct kunit *test, const void *p, char *desc) + +{ + struct kunit_ext_test_param *param =3D (struct kunit_ext_test_param *)p; + + snprintf(desc, KUNIT_PARAM_DESC_SIZE, "%s\n", param->desc); +} + +static int test_split_convert_param_init(struct kunit *test) +{ + size_t arr_size =3D ARRAY_SIZE(test_split_convert_params); + + kunit_register_params_array(test, test_split_convert_params, arr_size, + ext_get_desc); + return 0; +} + +/* + * Note that we use KUNIT_CASE_PARAM_WITH_INIT() instead of the more compa= ct + * KUNIT_ARRAY_PARAM() because the later currently has a limitation causin= g the + * output parsing to be prone to error. For more context: + * + * https://lore.kernel.org/linux-kselftest/aULJpTvJDw9ctUDe@li-dc0c254c-25= 7c-11b2-a85c-98b6c1322444.ibm.com/ + */ +static struct kunit_case extents_test_cases[] =3D { + KUNIT_CASE_PARAM_WITH_INIT(test_split_convert, kunit_array_gen_params, + test_split_convert_param_init, NULL), + {} +}; + +static struct kunit_suite extents_test_suite =3D { + .name =3D "ext4_extents_test", + .init =3D extents_kunit_init, + .exit =3D extents_kunit_exit, + .test_cases =3D extents_test_cases, +}; + +kunit_test_suites(&extents_test_suite); + +MODULE_LICENSE("GPL"); diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c index 3c288d4b19d2..08aa36854c11 100644 --- a/fs/ext4/extents.c +++ b/fs/ext4/extents.c @@ -32,6 +32,7 @@ #include "ext4_jbd2.h" #include "ext4_extents.h" #include "xattr.h" +#include =20 #include =20 @@ -197,6 +198,9 @@ static int __ext4_ext_dirty(const char *where, unsigned= int line, { int err; =20 + KUNIT_STATIC_STUB_REDIRECT(__ext4_ext_dirty, where, line, handle, inode, + path); + WARN_ON(!rwsem_is_locked(&EXT4_I(inode)->i_data_sem)); if (path->p_bh) { ext4_extent_block_csum_set(inode, ext_block_hdr(path->p_bh)); @@ -535,6 +539,8 @@ static void ext4_cache_extents(struct inode *inode, ext4_lblk_t prev =3D 0; int i; =20 + KUNIT_STATIC_STUB_REDIRECT(ext4_cache_extents, inode, eh); + for (i =3D le16_to_cpu(eh->eh_entries); i > 0; i--, ex++) { unsigned int status =3D EXTENT_STATUS_WRITTEN; ext4_lblk_t lblk =3D le32_to_cpu(ex->ee_block); @@ -898,6 +904,8 @@ ext4_find_extent(struct inode *inode, ext4_lblk_t block, int ret; gfp_t gfp_flags =3D GFP_NOFS; =20 + KUNIT_STATIC_STUB_REDIRECT(ext4_find_extent, inode, block, path, flags); + if (flags & EXT4_EX_NOFAIL) gfp_flags |=3D __GFP_NOFAIL; =20 @@ -1990,6 +1998,9 @@ ext4_ext_insert_extent(handle_t *handle, struct inode= *inode, ext4_lblk_t next; int mb_flags =3D 0, unwritten; =20 + KUNIT_STATIC_STUB_REDIRECT(ext4_ext_insert_extent, handle, inode, path, + newext, gb_flags); + if (gb_flags & EXT4_GET_BLOCKS_DELALLOC_RESERVE) mb_flags |=3D EXT4_MB_DELALLOC_RESERVED; if (unlikely(ext4_ext_get_actual_len(newext) =3D=3D 0)) { @@ -3134,8 +3145,10 @@ static void ext4_zeroout_es(struct inode *inode, str= uct ext4_extent *ex) ext4_fsblk_t ee_pblock; unsigned int ee_len; =20 - ee_block =3D le32_to_cpu(ex->ee_block); - ee_len =3D ext4_ext_get_actual_len(ex); + KUNIT_STATIC_STUB_REDIRECT(ext4_zeroout_es, inode, ex); + + ee_block =3D le32_to_cpu(ex->ee_block); + ee_len =3D ext4_ext_get_actual_len(ex); ee_pblock =3D ext4_ext_pblock(ex); =20 if (ee_len =3D=3D 0) @@ -3151,6 +3164,8 @@ static int ext4_ext_zeroout(struct inode *inode, stru= ct ext4_extent *ex) ext4_fsblk_t ee_pblock; unsigned int ee_len; =20 + KUNIT_STATIC_STUB_REDIRECT(ext4_ext_zeroout, inode, ex); + ee_len =3D ext4_ext_get_actual_len(ex); ee_pblock =3D ext4_ext_pblock(ex); return ext4_issue_zeroout(inode, le32_to_cpu(ex->ee_block), ee_pblock, @@ -6177,3 +6192,7 @@ int ext4_ext_clear_bb(struct inode *inode) ext4_free_ext_path(path); return 0; } + +#ifdef CONFIG_EXT4_KUNIT_TESTS +#include "extents-test.c" +#endif diff --git a/fs/ext4/extents_status.c b/fs/ext4/extents_status.c index fc83e7e2ca9e..6c1faf7c9f2a 100644 --- a/fs/ext4/extents_status.c +++ b/fs/ext4/extents_status.c @@ -16,6 +16,7 @@ #include "ext4.h" =20 #include +#include =20 /* * According to previous discussion in Ext4 Developer Workshop, we @@ -1627,6 +1628,8 @@ void ext4_es_remove_extent(struct inode *inode, ext4_= lblk_t lblk, int reserved =3D 0; struct extent_status *es =3D NULL; =20 + KUNIT_STATIC_STUB_REDIRECT(ext4_es_remove_extent, inode, lblk, len); + if (EXT4_SB(inode->i_sb)->s_mount_state & EXT4_FC_REPLAY) return; =20 diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c index cf0ffd799501..fafe673e5e17 100644 --- a/fs/ext4/inode.c +++ b/fs/ext4/inode.c @@ -48,6 +48,8 @@ #include "acl.h" #include "truncate.h" =20 +#include + #include =20 static void ext4_journalled_zero_new_buffers(handle_t *handle, @@ -400,6 +402,8 @@ int ext4_issue_zeroout(struct inode *inode, ext4_lblk_t= lblk, ext4_fsblk_t pblk, { int ret; =20 + KUNIT_STATIC_STUB_REDIRECT(ext4_issue_zeroout, inode, lblk, pblk, len); + if (IS_ENCRYPTED(inode) && S_ISREG(inode->i_mode)) return fscrypt_zeroout_range(inode, lblk, pblk, len); =20 --=20 2.52.0 From nobody Sat Feb 7 21:53:15 2026 Received: from mx0a-001b2d01.pphosted.com (mx0a-001b2d01.pphosted.com [148.163.156.1]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 16B95385EFE; Fri, 23 Jan 2026 06:26:04 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=148.163.156.1 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769149571; cv=none; b=FXeLVjChDLL5oZRjM4CnO/uETGUVxNYcRAVTTZhyETfhqW64M1KxBn2a78DGc1w5TDy0h+5BQk8haWMoQWpeB9Vp9zz7mSuA8uFivVRJpIp+fuAv/e5Njj2WVYqvAH4RxY2W9ME/kRafU6VDJPcmZE19jzY+s5EIwsy1NvO9JiQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769149571; c=relaxed/simple; bh=IV4PmrTNjcUDMmjTXQzvx25AvRNb9OxK7kSMJcrKHOY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=A0wA5CvZ8LFnuTJ7B516om37D3VrreaMbLFs49zFh+VzvzCQe1aRADsGBqWJbNg/OE0pUD/kybCD/NckxfGkE7RGPB6wrRd9TZtxyjVGXHLaOlUrY9Mk4cdWLkw7+IyJzS9cLyAJM5ovJIzJ/bU35NbF7aht6MzpONHADrLcwZg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.ibm.com; spf=pass smtp.mailfrom=linux.ibm.com; dkim=pass (2048-bit key) header.d=ibm.com header.i=@ibm.com header.b=XgG+pU1w; arc=none smtp.client-ip=148.163.156.1 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.ibm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.ibm.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=ibm.com header.i=@ibm.com header.b="XgG+pU1w" Received: from pps.filterd (m0360083.ppops.net [127.0.0.1]) by mx0a-001b2d01.pphosted.com (8.18.1.2/8.18.1.2) with ESMTP id 60MMqRmR028543; Fri, 23 Jan 2026 06:25:51 GMT DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=ibm.com; h=cc :content-transfer-encoding:date:from:in-reply-to:message-id :mime-version:references:subject:to; s=pp1; bh=BaJyhcVUwUfqs8Lns yhnQuFV+Fo+AqlXEKOiHTlg8FU=; b=XgG+pU1wEaTX17OjGP13hkjWJAMAFpXXZ gxiexF/RkTKfcNBiRaGbPQMKOlYqqdp79T1dBVlvBvrFp5oy8+bT7Vmw+ezexcu0 lbvm1uGAtJ8iQOCC8UuEO8o662VcY3rwqQOWnOBGnWof0Zcy704IMaxrSA/YAy+6 4s1yRzMnqcqQi7OiQudpi9MBOZOnkYlalgeidd3YGJnWF7wkNMOHX40qsTu4dLqo Q2hHcvktdoz60I47LvKV13Nyh9+yNFsz43ovPbmPmfNbyuwwMZg7mCV22TqyHT9k MT/xzgBQTIsab0AepIjhu2Qd5oI8+Qxo5tB1HnKnndfUi6q1/q8fQ== Received: from pps.reinject (localhost [127.0.0.1]) by mx0a-001b2d01.pphosted.com (PPS) with ESMTPS id 4bt60f1wn3-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Fri, 23 Jan 2026 06:25:51 +0000 (GMT) Received: from m0360083.ppops.net (m0360083.ppops.net [127.0.0.1]) by pps.reinject (8.18.1.12/8.18.0.8) with ESMTP id 60N6JemN028144; Fri, 23 Jan 2026 06:25:50 GMT Received: from ppma21.wdc07v.mail.ibm.com (5b.69.3da9.ip4.static.sl-reverse.com [169.61.105.91]) by mx0a-001b2d01.pphosted.com (PPS) with ESMTPS id 4bt60f1wmx-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Fri, 23 Jan 2026 06:25:50 +0000 (GMT) Received: from pps.filterd (ppma21.wdc07v.mail.ibm.com [127.0.0.1]) by ppma21.wdc07v.mail.ibm.com (8.18.1.2/8.18.1.2) with ESMTP id 60N47bwG027382; Fri, 23 Jan 2026 06:25:49 GMT Received: from smtprelay05.fra02v.mail.ibm.com ([9.218.2.225]) by ppma21.wdc07v.mail.ibm.com (PPS) with ESMTPS id 4brnrnfg4r-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Fri, 23 Jan 2026 06:25:49 +0000 Received: from smtpav02.fra02v.mail.ibm.com (smtpav02.fra02v.mail.ibm.com [10.20.54.101]) by smtprelay05.fra02v.mail.ibm.com (8.14.9/8.14.9/NCO v10.0) with ESMTP id 60N6Plt542926546 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK); Fri, 23 Jan 2026 06:25:47 GMT Received: from smtpav02.fra02v.mail.ibm.com (unknown [127.0.0.1]) by IMSVA (Postfix) with ESMTP id 16D0C20040; Fri, 23 Jan 2026 06:25:47 +0000 (GMT) Received: from smtpav02.fra02v.mail.ibm.com (unknown [127.0.0.1]) by IMSVA (Postfix) with ESMTP id E5EFD20043; Fri, 23 Jan 2026 06:25:44 +0000 (GMT) Received: from li-dc0c254c-257c-11b2-a85c-98b6c1322444.ibm.com (unknown [9.39.26.206]) by smtpav02.fra02v.mail.ibm.com (Postfix) with ESMTP; Fri, 23 Jan 2026 06:25:44 +0000 (GMT) From: Ojaswin Mujoo To: linux-ext4@vger.kernel.org, "Theodore Ts'o" Cc: Ritesh Harjani , Zhang Yi , Jan Kara , libaokun1@huawei.com, linux-kernel@vger.kernel.org Subject: [PATCH v4 2/8] ext4: kunit tests for higher level extent manipulation functions Date: Fri, 23 Jan 2026 11:55:33 +0530 Message-ID: <9d8ad32cb62f44999c0fe3545b44fc3113546c70.1769149131.git.ojaswin@linux.ibm.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-TM-AS-GCONF: 00 X-Authority-Analysis: v=2.4 cv=WMdyn3sR c=1 sm=1 tr=0 ts=6973146f cx=c_pps a=GFwsV6G8L6GxiO2Y/PsHdQ==:117 a=GFwsV6G8L6GxiO2Y/PsHdQ==:17 a=vUbySO9Y5rIA:10 a=VkNPw1HP01LnGYTKEx00:22 a=i0EeH86SAAAA:8 a=VnNF1IyMAAAA:8 a=Qhauk3kxTGDP0Of3FaYA:9 X-Proofpoint-GUID: f2fjUDaoNkKghANZpBD0c6rGVOjb4wRP X-Proofpoint-Spam-Details-Enc: AW1haW4tMjYwMTIzMDA0NiBTYWx0ZWRfX3yqqiI5z1shA spfk5f8HnpQEJ3AHox4SkNcRN3xEbFkv3lsK4IjE5RATr86jy6L8MFnn/aKXbyo4cU5LBgXQp0q fwrIQTQVjtzx9J7UqEjJm7CHB37aeXiU46I9KQbKfgYZFGbAh8TTlLSIkvR8cNURaceJA3vXMIx R/WKV5siUC1eIsY+n1OuOlA8gDE8aUxBmrzbxtiB+uRAhyNv0kT/LrwkapAvKmSA1ytpKJ+g0fm roORwZTri6AbrtsxUhhJeVE9jxt8p/v+Pj8+YZXxJbeTYeX+toq4eDwcOQqc9BwKu0+Z+GNATst txaACEZCrkWCIImZjO/sA2/KdaAMTnT0ksL3Xk3WvZg0OmE7bTYLcn+JTfj598V+3LneF8ybj0D I7ZwhYmC82aJ9DWkIUMxZOuyoGS354Awqcgv1Z2lNNBssl0Ce9zX2Bdd7Kcgn0NRywpfJ/JRrwF AN/37VhmrE+hsKsNbRQ== X-Proofpoint-ORIG-GUID: CAAdXD_aOeRoYfYnUNJ_T0RMrS0M4R_C X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1121,Hydra:6.1.20,FMLib:17.12.100.49 definitions=2026-01-22_06,2026-01-22_02,2025-10-01_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 phishscore=0 malwarescore=0 suspectscore=0 bulkscore=0 adultscore=0 impostorscore=0 spamscore=0 clxscore=1015 priorityscore=1501 lowpriorityscore=0 classifier=typeunknown authscore=0 authtc= authcc= route=outbound adjust=0 reason=mlx scancount=1 engine=8.19.0-2601150000 definitions=main-2601230046 Content-Type: text/plain; charset="utf-8" Add more kunit tests to cover the high level caller ext4_map_create_blocks(). We pass flags in a manner that covers the below function: 1. ext4_ext_handle_unwritten_extents() 1.1 - Split/Convert unwritten extent to written in endio convtext. 1.2 - Split/Convert unwritten extent to written in non endio context. 1.3 - Zeroout tests for the above 2 cases 2. convert_initialized_extent() - Convert written extent to unwritten during zero range Reviewed-by: Zhang Yi Reviewed-by: Jan Kara Signed-off-by: Ojaswin Mujoo --- fs/ext4/ext4.h | 4 + fs/ext4/extents-test.c | 359 ++++++++++++++++++++++++++++++++++++++- fs/ext4/extents_status.c | 3 + fs/ext4/inode.c | 8 +- 4 files changed, 365 insertions(+), 9 deletions(-) diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h index 9610602fe37b..b76966dc06c0 100644 --- a/fs/ext4/ext4.h +++ b/fs/ext4/ext4.h @@ -3804,6 +3804,10 @@ extern int ext4_convert_unwritten_io_end_vec(handle_= t *handle, ext4_io_end_t *io_end); extern int ext4_map_blocks(handle_t *handle, struct inode *inode, struct ext4_map_blocks *map, int flags); +extern int ext4_map_query_blocks(handle_t *handle, struct inode *inode, + struct ext4_map_blocks *map, int flags); +extern int ext4_map_create_blocks(handle_t *handle, struct inode *inode, + struct ext4_map_blocks *map, int flags); extern int ext4_ext_calc_credits_for_single_extent(struct inode *inode, int num, struct ext4_ext_path *path); diff --git a/fs/ext4/extents-test.c b/fs/ext4/extents-test.c index 15053c607cfd..4030fa5faca5 100644 --- a/fs/ext4/extents-test.c +++ b/fs/ext4/extents-test.c @@ -77,10 +77,18 @@ struct kunit_ext_data_state { ext4_lblk_t len_blk; }; =20 +enum kunit_test_types { + TEST_SPLIT_CONVERT, + TEST_CREATE_BLOCKS, +}; + struct kunit_ext_test_param { /* description of test */ char *desc; =20 + /* determines which function will be tested */ + int type; + /* is extent unwrit at beginning of test */ bool is_unwrit_at_start; =20 @@ -90,6 +98,9 @@ struct kunit_ext_test_param { /* map describing range to split */ struct ext4_map_blocks split_map; =20 + /* disable zeroout */ + bool disable_zeroout; + /* no of extents expected after split */ int nr_exp_ext; =20 @@ -131,6 +142,9 @@ static struct file_system_type ext_fs_type =3D { =20 static void extents_kunit_exit(struct kunit *test) { + struct ext4_sb_info *sbi =3D k_ctx.k_ei->vfs_inode.i_sb->s_fs_info; + + kfree(sbi); kfree(k_ctx.k_ei); kfree(k_ctx.k_data); } @@ -162,6 +176,13 @@ static void ext4_es_remove_extent_stub(struct inode *i= node, ext4_lblk_t lblk, return; } =20 +void ext4_es_insert_extent_stub(struct inode *inode, ext4_lblk_t lblk, + ext4_lblk_t len, ext4_fsblk_t pblk, + unsigned int status, bool delalloc_reserve_used) +{ + return; +} + static void ext4_zeroout_es_stub(struct inode *inode, struct ext4_extent *= ex) { return; @@ -220,6 +241,7 @@ static int extents_kunit_init(struct kunit *test) struct ext4_inode_info *ei; struct inode *inode; struct super_block *sb; + struct ext4_sb_info *sbi =3D NULL; struct kunit_ext_test_param *param =3D (struct kunit_ext_test_param *)(test->param_value); =20 @@ -237,7 +259,20 @@ static int extents_kunit_init(struct kunit *test) sb->s_blocksize =3D 4096; sb->s_blocksize_bits =3D 12; =20 - ei->i_disksize =3D (EXT_DATA_LBLK + EXT_DATA_LEN + 10) << sb->s_blocksize= _bits; + sbi =3D kzalloc(sizeof(struct ext4_sb_info), GFP_KERNEL); + if (sbi =3D=3D NULL) + return -ENOMEM; + + sbi->s_sb =3D sb; + sb->s_fs_info =3D sbi; + + if (!param || !param->disable_zeroout) + sbi->s_extent_max_zeroout_kb =3D 32; + + ei->i_disksize =3D (EXT_DATA_LBLK + EXT_DATA_LEN + 10) + << sb->s_blocksize_bits; + ei->i_flags =3D 0; + ext4_set_inode_flag(inode, EXT4_INODE_EXTENTS); inode->i_sb =3D sb; =20 k_ctx.k_data =3D kzalloc(EXT_DATA_LEN * 4096, GFP_KERNEL); @@ -262,7 +297,9 @@ static int extents_kunit_init(struct kunit *test) eh->eh_generation =3D 0; =20 /* - * add 1 extent in leaf node covering lblks [10,13) and pblk [100,103) + * add 1 extent in leaf node covering: + * - lblks: [EXT_DATA_LBLK, EXT_DATA_LBLK * + EXT_DATA_LEN) + * - pblks: [EXT_DATA_PBLK, EXT_DATA_PBLK + EXT_DATA_LEN) */ EXT_FIRST_EXTENT(eh)->ee_block =3D cpu_to_le32(EXT_DATA_LBLK); EXT_FIRST_EXTENT(eh)->ee_len =3D cpu_to_le16(EXT_DATA_LEN); @@ -277,6 +314,8 @@ static int extents_kunit_init(struct kunit *test) __ext4_ext_dirty_stub); kunit_activate_static_stub(test, ext4_es_remove_extent, ext4_es_remove_extent_stub); + kunit_activate_static_stub(test, ext4_es_insert_extent, + ext4_es_insert_extent_stub); kunit_activate_static_stub(test, ext4_zeroout_es, ext4_zeroout_es_stub); kunit_activate_static_stub(test, ext4_ext_zeroout, ext4_ext_zeroout_stub); kunit_activate_static_stub(test, ext4_issue_zeroout, @@ -301,6 +340,30 @@ static int check_buffer(char *buf, int c, int size) return 1; } =20 +/* + * Simulate a map block call by first calling ext4_map_query_blocks() to + * correctly populate map flags and pblk and then call the + * ext4_map_create_blocks() to do actual split and conversion. This is eas= ier + * than calling ext4_map_blocks() because that needs mocking a lot of unre= lated + * functions. + */ +static void ext4_map_create_blocks_helper(struct kunit *test, + struct inode *inode, + struct ext4_map_blocks *map, + int flags) +{ + int retval =3D 0; + + retval =3D ext4_map_query_blocks(NULL, inode, map, flags); + if (retval < 0) { + KUNIT_FAIL(test, + "ext4_map_query_blocks() failed. Cannot proceed\n"); + return; + } + + ext4_map_create_blocks(NULL, inode, map, flags); +} + static void test_split_convert(struct kunit *test) { struct ext4_ext_path *path; @@ -331,8 +394,18 @@ static void test_split_convert(struct kunit *test) =20 map.m_lblk =3D param->split_map.m_lblk; map.m_len =3D param->split_map.m_len; - ext4_split_convert_extents(NULL, inode, &map, path, - param->split_flags, NULL); + + switch (param->type) { + case TEST_SPLIT_CONVERT: + path =3D ext4_split_convert_extents(NULL, inode, &map, path, + param->split_flags, NULL); + break; + case TEST_CREATE_BLOCKS: + ext4_map_create_blocks_helper(test, inode, &map, param->split_flags); + break; + default: + KUNIT_FAIL(test, "param->type %d not support.", param->type); + } =20 path =3D ext4_find_extent(inode, EXT_DATA_LBLK, NULL, 0); ex =3D path->p_ext; @@ -386,6 +459,7 @@ static void test_split_convert(struct kunit *test) static const struct kunit_ext_test_param test_split_convert_params[] =3D { /* unwrit to writ splits */ { .desc =3D "split unwrit extent to 2 extents and convert 1st half writ", + .type =3D TEST_SPLIT_CONVERT, .is_unwrit_at_start =3D 1, .split_flags =3D EXT4_GET_BLOCKS_CONVERT, .split_map =3D { .m_lblk =3D EXT_DATA_LBLK, .m_len =3D 1 }, @@ -398,6 +472,7 @@ static const struct kunit_ext_test_param test_split_con= vert_params[] =3D { .is_unwrit =3D 1 } }, .is_zeroout_test =3D 0 }, { .desc =3D "split unwrit extent to 2 extents and convert 2nd half writ", + .type =3D TEST_SPLIT_CONVERT, .is_unwrit_at_start =3D 1, .split_flags =3D EXT4_GET_BLOCKS_CONVERT, .split_map =3D { .m_lblk =3D EXT_DATA_LBLK + 1, .m_len =3D EXT_DATA_LEN= - 1 }, @@ -410,6 +485,7 @@ static const struct kunit_ext_test_param test_split_con= vert_params[] =3D { .is_unwrit =3D 0 } }, .is_zeroout_test =3D 0 }, { .desc =3D "split unwrit extent to 3 extents and convert 2nd half to wri= t", + .type =3D TEST_SPLIT_CONVERT, .is_unwrit_at_start =3D 1, .split_flags =3D EXT4_GET_BLOCKS_CONVERT, .split_map =3D { .m_lblk =3D EXT_DATA_LBLK + 1, .m_len =3D EXT_DATA_LEN= - 2 }, @@ -427,6 +503,7 @@ static const struct kunit_ext_test_param test_split_con= vert_params[] =3D { =20 /* writ to unwrit splits */ { .desc =3D "split writ extent to 2 extents and convert 1st half unwrit", + .type =3D TEST_SPLIT_CONVERT, .is_unwrit_at_start =3D 0, .split_flags =3D EXT4_GET_BLOCKS_CONVERT_UNWRITTEN, .split_map =3D { .m_lblk =3D EXT_DATA_LBLK, .m_len =3D 1 }, @@ -439,6 +516,7 @@ static const struct kunit_ext_test_param test_split_con= vert_params[] =3D { .is_unwrit =3D 0 } }, .is_zeroout_test =3D 0 }, { .desc =3D "split writ extent to 2 extents and convert 2nd half unwrit", + .type =3D TEST_SPLIT_CONVERT, .is_unwrit_at_start =3D 0, .split_flags =3D EXT4_GET_BLOCKS_CONVERT_UNWRITTEN, .split_map =3D { .m_lblk =3D EXT_DATA_LBLK + 1, .m_len =3D EXT_DATA_LEN= - 1 }, @@ -451,6 +529,7 @@ static const struct kunit_ext_test_param test_split_con= vert_params[] =3D { .is_unwrit =3D 1 } }, .is_zeroout_test =3D 0 }, { .desc =3D "split writ extent to 3 extents and convert 2nd half to unwri= t", + .type =3D TEST_SPLIT_CONVERT, .is_unwrit_at_start =3D 0, .split_flags =3D EXT4_GET_BLOCKS_CONVERT_UNWRITTEN, .split_map =3D { .m_lblk =3D EXT_DATA_LBLK + 1, .m_len =3D EXT_DATA_LEN= - 2 }, @@ -471,6 +550,7 @@ static const struct kunit_ext_test_param test_split_con= vert_params[] =3D { */ /* unwrit to writ splits */ { .desc =3D "split unwrit extent to 2 extents and convert 1st half writ (= zeroout)", + .type =3D TEST_SPLIT_CONVERT, .is_unwrit_at_start =3D 1, .split_flags =3D EXT4_GET_BLOCKS_CONVERT, .split_map =3D { .m_lblk =3D EXT_DATA_LBLK, .m_len =3D 1 }, @@ -485,6 +565,7 @@ static const struct kunit_ext_test_param test_split_con= vert_params[] =3D { .off_blk =3D 1, .len_blk =3D EXT_DATA_LEN - 1 } } }, { .desc =3D "split unwrit extent to 2 extents and convert 2nd half writ (= zeroout)", + .type =3D TEST_SPLIT_CONVERT, .is_unwrit_at_start =3D 1, .split_flags =3D EXT4_GET_BLOCKS_CONVERT, .split_map =3D { .m_lblk =3D EXT_DATA_LBLK + 1, .m_len =3D EXT_DATA_LEN= - 1 }, @@ -499,6 +580,201 @@ static const struct kunit_ext_test_param test_split_c= onvert_params[] =3D { .off_blk =3D 1, .len_blk =3D EXT_DATA_LEN - 1 } } }, { .desc =3D "split unwrit extent to 3 extents and convert 2nd half writ (= zeroout)", + .type =3D TEST_SPLIT_CONVERT, + .is_unwrit_at_start =3D 1, + .split_flags =3D EXT4_GET_BLOCKS_CONVERT, + .split_map =3D { .m_lblk =3D EXT_DATA_LBLK + 1, .m_len =3D EXT_DATA_LEN= - 2 }, + .nr_exp_ext =3D 1, + .exp_ext_state =3D { { .ex_lblk =3D EXT_DATA_LBLK, + .ex_len =3D EXT_DATA_LEN, + .is_unwrit =3D 0 } }, + .is_zeroout_test =3D 1, + .nr_exp_data_segs =3D 3, + .exp_data_state =3D { { .exp_char =3D 0, .off_blk =3D 0, .len_blk =3D 1= }, + { .exp_char =3D 'X', + .off_blk =3D 1, + .len_blk =3D EXT_DATA_LEN - 2 }, + { .exp_char =3D 0, + .off_blk =3D EXT_DATA_LEN - 1, + .len_blk =3D 1 } } }, +}; + +/* Tests to trigger ext4_ext_map_blocks() -> convert_initialized_extent() = */ +static const struct kunit_ext_test_param test_convert_initialized_params[]= =3D { + /* writ to unwrit splits */ + { .desc =3D "split writ extent to 2 extents and convert 1st half unwrit", + .type =3D TEST_CREATE_BLOCKS, + .split_flags =3D EXT4_GET_BLOCKS_CONVERT_UNWRITTEN, + .is_unwrit_at_start =3D 0, + .split_map =3D { .m_lblk =3D EXT_DATA_LBLK, .m_len =3D 1 }, + .nr_exp_ext =3D 2, + .exp_ext_state =3D { { .ex_lblk =3D EXT_DATA_LBLK, + .ex_len =3D 1, + .is_unwrit =3D 1 }, + { .ex_lblk =3D EXT_DATA_LBLK + 1, + .ex_len =3D EXT_DATA_LEN - 1, + .is_unwrit =3D 0 } }, + .is_zeroout_test =3D 0 }, + { .desc =3D "split writ extent to 2 extents and convert 2nd half unwrit", + .type =3D TEST_CREATE_BLOCKS, + .split_flags =3D EXT4_GET_BLOCKS_CONVERT_UNWRITTEN, + .is_unwrit_at_start =3D 0, + .split_map =3D { .m_lblk =3D EXT_DATA_LBLK + 1, .m_len =3D EXT_DATA_LEN= - 1 }, + .nr_exp_ext =3D 2, + .exp_ext_state =3D { { .ex_lblk =3D EXT_DATA_LBLK, + .ex_len =3D 1, + .is_unwrit =3D 0 }, + { .ex_lblk =3D EXT_DATA_LBLK + 1, + .ex_len =3D EXT_DATA_LEN - 1, + .is_unwrit =3D 1 } }, + .is_zeroout_test =3D 0 }, + { .desc =3D "split writ extent to 3 extents and convert 2nd half to unwri= t", + .type =3D TEST_CREATE_BLOCKS, + .split_flags =3D EXT4_GET_BLOCKS_CONVERT_UNWRITTEN, + .is_unwrit_at_start =3D 0, + .split_map =3D { .m_lblk =3D EXT_DATA_LBLK + 1, .m_len =3D EXT_DATA_LEN= - 2 }, + .nr_exp_ext =3D 3, + .exp_ext_state =3D { { .ex_lblk =3D EXT_DATA_LBLK, + .ex_len =3D 1, + .is_unwrit =3D 0 }, + { .ex_lblk =3D EXT_DATA_LBLK + 1, + .ex_len =3D EXT_DATA_LEN - 2, + .is_unwrit =3D 1 }, + { .ex_lblk =3D EXT_DATA_LBLK + 1 + (EXT_DATA_LEN - 2), + .ex_len =3D 1, + .is_unwrit =3D 0 } }, + .is_zeroout_test =3D 0 }, +}; + +/* Tests to trigger ext4_ext_map_blocks() -> ext4_ext_handle_unwritten_exn= tents() */ +static const struct kunit_ext_test_param test_handle_unwritten_params[] = =3D { + /* unwrit to writ splits via endio path */ + { .desc =3D "split unwrit extent to 2 extents and convert 1st half writ (= endio)", + .type =3D TEST_CREATE_BLOCKS, + .is_unwrit_at_start =3D 1, + .split_flags =3D EXT4_GET_BLOCKS_CONVERT, + .split_map =3D { .m_lblk =3D EXT_DATA_LBLK, .m_len =3D 1 }, + .nr_exp_ext =3D 2, + .exp_ext_state =3D { { .ex_lblk =3D EXT_DATA_LBLK, + .ex_len =3D 1, + .is_unwrit =3D 0 }, + { .ex_lblk =3D EXT_DATA_LBLK + 1, + .ex_len =3D EXT_DATA_LEN - 1, + .is_unwrit =3D 1 } }, + .is_zeroout_test =3D 0 }, + { .desc =3D "split unwrit extent to 2 extents and convert 2nd half writ (= endio)", + .type =3D TEST_CREATE_BLOCKS, + .is_unwrit_at_start =3D 1, + .split_flags =3D EXT4_GET_BLOCKS_CONVERT, + .split_map =3D { .m_lblk =3D EXT_DATA_LBLK + 1, .m_len =3D EXT_DATA_LEN= - 1 }, + .nr_exp_ext =3D 2, + .exp_ext_state =3D { { .ex_lblk =3D EXT_DATA_LBLK, + .ex_len =3D 1, + .is_unwrit =3D 1 }, + { .ex_lblk =3D EXT_DATA_LBLK + 1, + .ex_len =3D EXT_DATA_LEN - 1, + .is_unwrit =3D 0 } }, + .is_zeroout_test =3D 0 }, + { .desc =3D "split unwrit extent to 3 extents and convert 2nd half to wri= t (endio)", + .type =3D TEST_CREATE_BLOCKS, + .is_unwrit_at_start =3D 1, + .split_flags =3D EXT4_GET_BLOCKS_CONVERT, + .split_map =3D { .m_lblk =3D EXT_DATA_LBLK + 1, .m_len =3D EXT_DATA_LEN= - 2 }, + .nr_exp_ext =3D 3, + .exp_ext_state =3D { { .ex_lblk =3D EXT_DATA_LBLK, + .ex_len =3D 1, + .is_unwrit =3D 1 }, + { .ex_lblk =3D EXT_DATA_LBLK + 1, + .ex_len =3D EXT_DATA_LEN - 2, + .is_unwrit =3D 0 }, + { .ex_lblk =3D EXT_DATA_LBLK + 1 + (EXT_DATA_LEN - 2), + .ex_len =3D 1, + .is_unwrit =3D 1 } }, + .is_zeroout_test =3D 0 }, + + /* unwrit to writ splits via non-endio path */ + { .desc =3D "split unwrit extent to 2 extents and convert 1st half writ (= non endio)", + .type =3D TEST_CREATE_BLOCKS, + .is_unwrit_at_start =3D 1, + .split_flags =3D EXT4_GET_BLOCKS_CREATE, + .split_map =3D { .m_lblk =3D EXT_DATA_LBLK, .m_len =3D 1 }, + .nr_exp_ext =3D 2, + .disable_zeroout =3D true, + .exp_ext_state =3D { { .ex_lblk =3D EXT_DATA_LBLK, + .ex_len =3D 1, + .is_unwrit =3D 0 }, + { .ex_lblk =3D EXT_DATA_LBLK + 1, + .ex_len =3D EXT_DATA_LEN - 1, + .is_unwrit =3D 1 } }, + .is_zeroout_test =3D 0 }, + { .desc =3D "split unwrit extent to 2 extents and convert 2nd half writ (= non endio)", + .type =3D TEST_CREATE_BLOCKS, + .is_unwrit_at_start =3D 1, + .split_flags =3D EXT4_GET_BLOCKS_CREATE, + .split_map =3D { .m_lblk =3D EXT_DATA_LBLK + 1, .m_len =3D EXT_DATA_LEN= - 1 }, + .nr_exp_ext =3D 2, + .disable_zeroout =3D true, + .exp_ext_state =3D { { .ex_lblk =3D EXT_DATA_LBLK, + .ex_len =3D 1, + .is_unwrit =3D 1 }, + { .ex_lblk =3D EXT_DATA_LBLK + 1, + .ex_len =3D EXT_DATA_LEN - 1, + .is_unwrit =3D 0 } }, + .is_zeroout_test =3D 0 }, + { .desc =3D "split unwrit extent to 3 extents and convert 2nd half to wri= t (non endio)", + .type =3D TEST_CREATE_BLOCKS, + .is_unwrit_at_start =3D 1, + .split_flags =3D EXT4_GET_BLOCKS_CREATE, + .split_map =3D { .m_lblk =3D EXT_DATA_LBLK + 1, .m_len =3D EXT_DATA_LEN= - 2 }, + .nr_exp_ext =3D 3, + .disable_zeroout =3D true, + .exp_ext_state =3D { { .ex_lblk =3D EXT_DATA_LBLK, + .ex_len =3D 1, + .is_unwrit =3D 1 }, + { .ex_lblk =3D EXT_DATA_LBLK + 1, + .ex_len =3D EXT_DATA_LEN - 2, + .is_unwrit =3D 0 }, + { .ex_lblk =3D EXT_DATA_LBLK + 1 + (EXT_DATA_LEN - 2), + .ex_len =3D 1, + .is_unwrit =3D 1 } }, + .is_zeroout_test =3D 0 }, + + /* + * ***** zeroout tests ***** + */ + /* unwrit to writ splits (endio)*/ + { .desc =3D "split unwrit extent to 2 extents and convert 1st half writ (= endio, zeroout)", + .type =3D TEST_CREATE_BLOCKS, + .is_unwrit_at_start =3D 1, + .split_flags =3D EXT4_GET_BLOCKS_CONVERT, + .split_map =3D { .m_lblk =3D EXT_DATA_LBLK, .m_len =3D 1 }, + .nr_exp_ext =3D 1, + .exp_ext_state =3D { { .ex_lblk =3D EXT_DATA_LBLK, + .ex_len =3D EXT_DATA_LEN, + .is_unwrit =3D 0 } }, + .is_zeroout_test =3D 1, + .nr_exp_data_segs =3D 2, + .exp_data_state =3D { { .exp_char =3D 'X', .off_blk =3D 0, .len_blk =3D= 1 }, + { .exp_char =3D 0, + .off_blk =3D 1, + .len_blk =3D EXT_DATA_LEN - 1 } } }, + { .desc =3D "split unwrit extent to 2 extents and convert 2nd half writ (= endio, zeroout)", + .type =3D TEST_CREATE_BLOCKS, + .is_unwrit_at_start =3D 1, + .split_flags =3D EXT4_GET_BLOCKS_CONVERT, + .split_map =3D { .m_lblk =3D EXT_DATA_LBLK + 1, .m_len =3D EXT_DATA_LEN= - 1 }, + .nr_exp_ext =3D 1, + .exp_ext_state =3D { { .ex_lblk =3D EXT_DATA_LBLK, + .ex_len =3D EXT_DATA_LEN, + .is_unwrit =3D 0 } }, + .is_zeroout_test =3D 1, + .nr_exp_data_segs =3D 2, + .exp_data_state =3D { { .exp_char =3D 0, .off_blk =3D 0, .len_blk =3D 1= }, + { .exp_char =3D 'X', + .off_blk =3D 1, + .len_blk =3D EXT_DATA_LEN - 1 } } }, + { .desc =3D "split unwrit extent to 3 extents and convert 2nd half writ (= endio, zeroout)", + .type =3D TEST_CREATE_BLOCKS, .is_unwrit_at_start =3D 1, .split_flags =3D EXT4_GET_BLOCKS_CONVERT, .split_map =3D { .m_lblk =3D EXT_DATA_LBLK + 1, .m_len =3D EXT_DATA_LEN= - 2 }, @@ -515,6 +791,56 @@ static const struct kunit_ext_test_param test_split_co= nvert_params[] =3D { { .exp_char =3D 0, .off_blk =3D EXT_DATA_LEN - 1, .len_blk =3D 1 } } }, + + /* unwrit to writ splits (non-endio)*/ + { .desc =3D "split unwrit extent to 2 extents and convert 1st half writ (= non-endio, zeroout)", + .type =3D TEST_CREATE_BLOCKS, + .is_unwrit_at_start =3D 1, + .split_flags =3D EXT4_GET_BLOCKS_CREATE, + .split_map =3D { .m_lblk =3D EXT_DATA_LBLK, .m_len =3D 1 }, + .nr_exp_ext =3D 1, + .exp_ext_state =3D { { .ex_lblk =3D EXT_DATA_LBLK, + .ex_len =3D EXT_DATA_LEN, + .is_unwrit =3D 0 } }, + .is_zeroout_test =3D 1, + .nr_exp_data_segs =3D 2, + .exp_data_state =3D { { .exp_char =3D 'X', .off_blk =3D 0, .len_blk =3D= 1 }, + { .exp_char =3D 0, + .off_blk =3D 1, + .len_blk =3D EXT_DATA_LEN - 1 } } }, + { .desc =3D "split unwrit extent to 2 extents and convert 2nd half writ (= non-endio, zeroout)", + .type =3D TEST_CREATE_BLOCKS, + .is_unwrit_at_start =3D 1, + .split_flags =3D EXT4_GET_BLOCKS_CREATE, + .split_map =3D { .m_lblk =3D EXT_DATA_LBLK + 1, .m_len =3D EXT_DATA_LEN= - 1 }, + .nr_exp_ext =3D 1, + .exp_ext_state =3D { { .ex_lblk =3D EXT_DATA_LBLK, + .ex_len =3D EXT_DATA_LEN, + .is_unwrit =3D 0 } }, + .is_zeroout_test =3D 1, + .nr_exp_data_segs =3D 2, + .exp_data_state =3D { { .exp_char =3D 0, .off_blk =3D 0, .len_blk =3D 1= }, + { .exp_char =3D 'X', + .off_blk =3D 1, + .len_blk =3D EXT_DATA_LEN - 1 } } }, + { .desc =3D "split unwrit extent to 3 extents and convert 2nd half writ (= non-endio, zeroout)", + .type =3D TEST_CREATE_BLOCKS, + .is_unwrit_at_start =3D 1, + .split_flags =3D EXT4_GET_BLOCKS_CREATE, + .split_map =3D { .m_lblk =3D EXT_DATA_LBLK + 1, .m_len =3D EXT_DATA_LEN= - 2 }, + .nr_exp_ext =3D 1, + .exp_ext_state =3D { { .ex_lblk =3D EXT_DATA_LBLK, + .ex_len =3D EXT_DATA_LEN, + .is_unwrit =3D 0 } }, + .is_zeroout_test =3D 1, + .nr_exp_data_segs =3D 3, + .exp_data_state =3D { { .exp_char =3D 0, .off_blk =3D 0, .len_blk =3D 1= }, + { .exp_char =3D 'X', + .off_blk =3D 1, + .len_blk =3D EXT_DATA_LEN - 2 }, + { .exp_char =3D 0, + .off_blk =3D EXT_DATA_LEN - 1, + .len_blk =3D 1 } } }, }; =20 static void ext_get_desc(struct kunit *test, const void *p, char *desc) @@ -522,7 +848,8 @@ static void ext_get_desc(struct kunit *test, const void= *p, char *desc) { struct kunit_ext_test_param *param =3D (struct kunit_ext_test_param *)p; =20 - snprintf(desc, KUNIT_PARAM_DESC_SIZE, "%s\n", param->desc); + snprintf(desc, KUNIT_PARAM_DESC_SIZE, "%s %s\n", param->desc, + (param->type & TEST_CREATE_BLOCKS) ? "(highlevel)" : ""); } =20 static int test_split_convert_param_init(struct kunit *test) @@ -534,6 +861,24 @@ static int test_split_convert_param_init(struct kunit = *test) return 0; } =20 +static int test_convert_initialized_param_init(struct kunit *test) +{ + size_t arr_size =3D ARRAY_SIZE(test_convert_initialized_params); + + kunit_register_params_array(test, test_convert_initialized_params, + arr_size, ext_get_desc); + return 0; +} + +static int test_handle_unwritten_init(struct kunit *test) +{ + size_t arr_size =3D ARRAY_SIZE(test_handle_unwritten_params); + + kunit_register_params_array(test, test_handle_unwritten_params, + arr_size, ext_get_desc); + return 0; +} + /* * Note that we use KUNIT_CASE_PARAM_WITH_INIT() instead of the more compa= ct * KUNIT_ARRAY_PARAM() because the later currently has a limitation causin= g the @@ -544,6 +889,10 @@ static int test_split_convert_param_init(struct kunit = *test) static struct kunit_case extents_test_cases[] =3D { KUNIT_CASE_PARAM_WITH_INIT(test_split_convert, kunit_array_gen_params, test_split_convert_param_init, NULL), + KUNIT_CASE_PARAM_WITH_INIT(test_split_convert, kunit_array_gen_params, + test_convert_initialized_param_init, NULL), + KUNIT_CASE_PARAM_WITH_INIT(test_split_convert, kunit_array_gen_params, + test_handle_unwritten_init, NULL), {} }; =20 diff --git a/fs/ext4/extents_status.c b/fs/ext4/extents_status.c index 6c1faf7c9f2a..095ccb7ba4ba 100644 --- a/fs/ext4/extents_status.c +++ b/fs/ext4/extents_status.c @@ -916,6 +916,9 @@ void ext4_es_insert_extent(struct inode *inode, ext4_lb= lk_t lblk, struct pending_reservation *pr =3D NULL; bool revise_pending =3D false; =20 + KUNIT_STATIC_STUB_REDIRECT(ext4_es_insert_extent, inode, lblk, len, + pblk, status, delalloc_reserve_used); + if (EXT4_SB(inode->i_sb)->s_mount_state & EXT4_FC_REPLAY) return; =20 diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c index fafe673e5e17..15ba4d42982f 100644 --- a/fs/ext4/inode.c +++ b/fs/ext4/inode.c @@ -541,8 +541,8 @@ static int ext4_map_query_blocks_next_in_leaf(handle_t = *handle, return map->m_len; } =20 -static int ext4_map_query_blocks(handle_t *handle, struct inode *inode, - struct ext4_map_blocks *map, int flags) +int ext4_map_query_blocks(handle_t *handle, struct inode *inode, + struct ext4_map_blocks *map, int flags) { unsigned int status; int retval; @@ -588,8 +588,8 @@ static int ext4_map_query_blocks(handle_t *handle, stru= ct inode *inode, return retval; } =20 -static int ext4_map_create_blocks(handle_t *handle, struct inode *inode, - struct ext4_map_blocks *map, int flags) +int ext4_map_create_blocks(handle_t *handle, struct inode *inode, + struct ext4_map_blocks *map, int flags) { unsigned int status; int err, retval =3D 0; --=20 2.52.0 From nobody Sat Feb 7 21:53:15 2026 Received: from mx0b-001b2d01.pphosted.com (mx0b-001b2d01.pphosted.com [148.163.158.5]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 743BE35FF4B; Fri, 23 Jan 2026 06:26:06 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=148.163.158.5 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769149573; cv=none; b=akHIBpZAx0D7KBC62DeLHIDuQO8hm/mMe79gNn5QHLp8IWTY6G3PUEIQB/7R02Ji+awGklXSvY5nQa78QXT4xdcLLVc8olK2GTRHAbwUVQr3H3ALnSIQ6pLIuqy7ish/3jMlAZJC1pfIFI0BM5UZwuhjeypeKOypD5P9bRHP4as= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769149573; c=relaxed/simple; bh=0RoNooc4CjRO+Ri6Ttob59ZeDvmEf1Fi6CNq89SDcCA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=dLOSwtd82wIgD5F/vc3quiGBweQO9dVmrVQznTyfONUgZzv2RYPrfCWTBs1RdAWEa5s/omx9O7e7GT2KEOvV+c7Y9MT83c4MFrxPHEzdJJ84hyfmozL53VTcHUc1Bj7NlauBSXZAVyMxE4Obkohf9SbpHbTHFVdp1D0w6iFiPKs= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.ibm.com; spf=pass smtp.mailfrom=linux.ibm.com; dkim=pass (2048-bit key) header.d=ibm.com header.i=@ibm.com header.b=S9+TXXrC; arc=none smtp.client-ip=148.163.158.5 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.ibm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.ibm.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=ibm.com header.i=@ibm.com header.b="S9+TXXrC" Received: from pps.filterd (m0360072.ppops.net [127.0.0.1]) by mx0a-001b2d01.pphosted.com (8.18.1.2/8.18.1.2) with ESMTP id 60N3fh49021241; Fri, 23 Jan 2026 06:25:52 GMT DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=ibm.com; h=cc :content-transfer-encoding:date:from:in-reply-to:message-id :mime-version:references:subject:to; s=pp1; bh=3BmcwHhyK/Z+Z2LtW 2Tmgz7blpJ82bbpHkBLNVmqiPY=; b=S9+TXXrCXXUm9oTChRBPrn0pm2zwIevjM qheK4EgRCPHjPeXwrVviizPWkN/X5RJhlpjCaJ2oJ/ReQz/VOoNUm5+am+mP6wbX RTMRa5+Qelnvm41cUn3nMFn2tXcqwighn1Rlf5n2wJfn8JUg1U+XQqbdkoRo/Xf+ Az6BtuduboH0uL9bbHSn3+nHA8MA2xb3RC2CdQgI3En7FOcaco8yEJu59dpMmquO au9Xv/cuRcDI8Xbtt71R7Jri/A7fv9Rzkpcz616+2RNSDFqsleMeaXsJ6/QEsJSX WCuPP4kg+L5RTg5dxQ/7pXWBjifJvkbfyk7Uo+OG/Fq1flRg9ZhqA== Received: from pps.reinject (localhost [127.0.0.1]) by mx0a-001b2d01.pphosted.com (PPS) with ESMTPS id 4bt612h1br-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Fri, 23 Jan 2026 06:25:52 +0000 (GMT) Received: from m0360072.ppops.net (m0360072.ppops.net [127.0.0.1]) by pps.reinject (8.18.1.12/8.18.0.8) with ESMTP id 60N6GqxR016136; Fri, 23 Jan 2026 06:25:52 GMT Received: from ppma13.dal12v.mail.ibm.com (dd.9e.1632.ip4.static.sl-reverse.com [50.22.158.221]) by mx0a-001b2d01.pphosted.com (PPS) with ESMTPS id 4bt612h1bm-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Fri, 23 Jan 2026 06:25:52 +0000 (GMT) Received: from pps.filterd (ppma13.dal12v.mail.ibm.com [127.0.0.1]) by ppma13.dal12v.mail.ibm.com (8.18.1.2/8.18.1.2) with ESMTP id 60N4kEbi001171; Fri, 23 Jan 2026 06:25:51 GMT Received: from smtprelay02.fra02v.mail.ibm.com ([9.218.2.226]) by ppma13.dal12v.mail.ibm.com (PPS) with ESMTPS id 4brpyk7a3r-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Fri, 23 Jan 2026 06:25:51 +0000 Received: from smtpav02.fra02v.mail.ibm.com (smtpav02.fra02v.mail.ibm.com [10.20.54.101]) by smtprelay02.fra02v.mail.ibm.com (8.14.9/8.14.9/NCO v10.0) with ESMTP id 60N6PnCR44106048 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK); Fri, 23 Jan 2026 06:25:49 GMT Received: from smtpav02.fra02v.mail.ibm.com (unknown [127.0.0.1]) by IMSVA (Postfix) with ESMTP id 5497420043; Fri, 23 Jan 2026 06:25:49 +0000 (GMT) Received: from smtpav02.fra02v.mail.ibm.com (unknown [127.0.0.1]) by IMSVA (Postfix) with ESMTP id 7B57620040; Fri, 23 Jan 2026 06:25:47 +0000 (GMT) Received: from li-dc0c254c-257c-11b2-a85c-98b6c1322444.ibm.com (unknown [9.39.26.206]) by smtpav02.fra02v.mail.ibm.com (Postfix) with ESMTP; Fri, 23 Jan 2026 06:25:47 +0000 (GMT) From: Ojaswin Mujoo To: linux-ext4@vger.kernel.org, "Theodore Ts'o" Cc: Ritesh Harjani , Zhang Yi , Jan Kara , libaokun1@huawei.com, linux-kernel@vger.kernel.org Subject: [PATCH v4 3/8] ext4: Add extent status cache support to kunit tests Date: Fri, 23 Jan 2026 11:55:34 +0530 Message-ID: <5f9d2668feeb89a3f3e9d03dadab8c10cbea3741.1769149131.git.ojaswin@linux.ibm.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-TM-AS-GCONF: 00 X-Proofpoint-GUID: lMJLXpxREvnMh1QTrzbfoBo4H2PzqBAw X-Authority-Analysis: v=2.4 cv=LaIxKzfi c=1 sm=1 tr=0 ts=69731470 cx=c_pps a=AfN7/Ok6k8XGzOShvHwTGQ==:117 a=AfN7/Ok6k8XGzOShvHwTGQ==:17 a=vUbySO9Y5rIA:10 a=VkNPw1HP01LnGYTKEx00:22 a=i0EeH86SAAAA:8 a=VnNF1IyMAAAA:8 a=mO1zcom0dC79SphzpucA:9 X-Proofpoint-ORIG-GUID: R74iuf1YfTWqsigcuEZ3Hkfme6a56osn X-Proofpoint-Spam-Details-Enc: AW1haW4tMjYwMTIzMDA0NiBTYWx0ZWRfX8Pj/1zkW0KmN 04A5tO8vn8G7szQ8bsht1dv/nw0eLYaNswTmR+gUo18K+YHaosW4mqqVi9dMubRcGXimZqLjGaJ lSQ6GnRiWPUN5/D1pnH3VjkBkaMzFed72bIH3tWptBnmslA2zrzjTwKm2yDjzQWyRxfRZhN3TIv ++yeku862EbGPRazkkc/WHzbzfLAOycYlCbdMe5x7pAFTlig+0BzpVZQq7m/1RCNMElVlCxf4JI 5FN5iVBFGv02h3TUt5awq130l82RcQFIntKjXCWz9jdJpeO0Obk1hyXZR9vDyuU5J3+aT9Uf41H g0X7WSNCbONVMIU56iciVwXN707Nkc9bNh1bsoeZJhbNRhcyqh/Nbfs/ryxPthkaVCHpm14tgNm 39ca20cxH8KNJaJPcadFn9qIOabBVX4XH8a21/R/L6KEdr8hYlBlXGpQhz0nQMo6q4cjKwZRHPn jiwY8sLzSfjx/pzkucQ== X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1121,Hydra:6.1.20,FMLib:17.12.100.49 definitions=2026-01-22_06,2026-01-22_02,2025-10-01_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 bulkscore=0 adultscore=0 phishscore=0 priorityscore=1501 lowpriorityscore=0 suspectscore=0 clxscore=1015 impostorscore=0 spamscore=0 malwarescore=0 classifier=typeunknown authscore=0 authtc= authcc= route=outbound adjust=0 reason=mlx scancount=1 engine=8.19.0-2601150000 definitions=main-2601230046 Content-Type: text/plain; charset="utf-8" Add support in Kunit tests to ensure that the extent status cache is also in sync after the extent split and conversion operations. Reviewed-by: Zhang Yi Reviewed-by: Jan Kara Signed-off-by: Ojaswin Mujoo --- fs/ext4/extents-test.c | 103 ++++++++++++++++++++++++--------------- fs/ext4/extents.c | 2 - fs/ext4/extents_status.c | 5 -- 3 files changed, 63 insertions(+), 47 deletions(-) diff --git a/fs/ext4/extents-test.c b/fs/ext4/extents-test.c index 4030fa5faca5..3176bf7686b5 100644 --- a/fs/ext4/extents-test.c +++ b/fs/ext4/extents-test.c @@ -149,12 +149,6 @@ static void extents_kunit_exit(struct kunit *test) kfree(k_ctx.k_data); } =20 -static void ext4_cache_extents_stub(struct inode *inode, - struct ext4_extent_header *eh) -{ - return; -} - static int __ext4_ext_dirty_stub(const char *where, unsigned int line, handle_t *handle, struct inode *inode, struct ext4_ext_path *path) @@ -170,24 +164,6 @@ ext4_ext_insert_extent_stub(handle_t *handle, struct i= node *inode, return ERR_PTR(-ENOSPC); } =20 -static void ext4_es_remove_extent_stub(struct inode *inode, ext4_lblk_t lb= lk, - ext4_lblk_t len) -{ - return; -} - -void ext4_es_insert_extent_stub(struct inode *inode, ext4_lblk_t lblk, - ext4_lblk_t len, ext4_fsblk_t pblk, - unsigned int status, bool delalloc_reserve_used) -{ - return; -} - -static void ext4_zeroout_es_stub(struct inode *inode, struct ext4_extent *= ex) -{ - return; -} - /* * We will zeroout the equivalent range in the data area */ @@ -244,13 +220,7 @@ static int extents_kunit_init(struct kunit *test) struct ext4_sb_info *sbi =3D NULL; struct kunit_ext_test_param *param =3D (struct kunit_ext_test_param *)(test->param_value); - - /* setup the mock inode */ - k_ctx.k_ei =3D kzalloc(sizeof(struct ext4_inode_info), GFP_KERNEL); - if (k_ctx.k_ei =3D=3D NULL) - return -ENOMEM; - ei =3D k_ctx.k_ei; - inode =3D &ei->vfs_inode; + int err; =20 sb =3D sget(&ext_fs_type, NULL, ext_set, 0, NULL); if (IS_ERR(sb)) @@ -269,6 +239,24 @@ static int extents_kunit_init(struct kunit *test) if (!param || !param->disable_zeroout) sbi->s_extent_max_zeroout_kb =3D 32; =20 + /* setup the mock inode */ + k_ctx.k_ei =3D kzalloc(sizeof(struct ext4_inode_info), GFP_KERNEL); + if (k_ctx.k_ei =3D=3D NULL) + return -ENOMEM; + ei =3D k_ctx.k_ei; + inode =3D &ei->vfs_inode; + + err =3D ext4_es_register_shrinker(sbi); + if (err) + return err; + + ext4_es_init_tree(&ei->i_es_tree); + rwlock_init(&ei->i_es_lock); + INIT_LIST_HEAD(&ei->i_es_list); + ei->i_es_all_nr =3D 0; + ei->i_es_shk_nr =3D 0; + ei->i_es_shrink_lblk =3D 0; + ei->i_disksize =3D (EXT_DATA_LBLK + EXT_DATA_LEN + 10) << sb->s_blocksize_bits; ei->i_flags =3D 0; @@ -307,16 +295,15 @@ static int extents_kunit_init(struct kunit *test) if (!param || param->is_unwrit_at_start) ext4_ext_mark_unwritten(EXT_FIRST_EXTENT(eh)); =20 + ext4_es_insert_extent(inode, EXT_DATA_LBLK, EXT_DATA_LEN, EXT_DATA_PBLK, + ext4_ext_is_unwritten(EXT_FIRST_EXTENT(eh)) ? + EXTENT_STATUS_UNWRITTEN : + EXTENT_STATUS_WRITTEN, + 0); + /* Add stubs */ - kunit_activate_static_stub(test, ext4_cache_extents, - ext4_cache_extents_stub); kunit_activate_static_stub(test, __ext4_ext_dirty, __ext4_ext_dirty_stub); - kunit_activate_static_stub(test, ext4_es_remove_extent, - ext4_es_remove_extent_stub); - kunit_activate_static_stub(test, ext4_es_insert_extent, - ext4_es_insert_extent_stub); - kunit_activate_static_stub(test, ext4_zeroout_es, ext4_zeroout_es_stub); kunit_activate_static_stub(test, ext4_ext_zeroout, ext4_ext_zeroout_stub); kunit_activate_static_stub(test, ext4_issue_zeroout, ext4_issue_zeroout_stub); @@ -381,7 +368,7 @@ static void test_split_convert(struct kunit *test) kunit_activate_static_stub(test, ext4_ext_insert_extent, ext4_ext_insert_extent_stub); =20 - path =3D ext4_find_extent(inode, EXT_DATA_LBLK, NULL, 0); + path =3D ext4_find_extent(inode, EXT_DATA_LBLK, NULL, EXT4_EX_NOCACHE); ex =3D path->p_ext; KUNIT_EXPECT_EQ(test, EXT_DATA_LBLK, le32_to_cpu(ex->ee_block)); KUNIT_EXPECT_EQ(test, EXT_DATA_LEN, ext4_ext_get_actual_len(ex)); @@ -407,11 +394,14 @@ static void test_split_convert(struct kunit *test) KUNIT_FAIL(test, "param->type %d not support.", param->type); } =20 - path =3D ext4_find_extent(inode, EXT_DATA_LBLK, NULL, 0); + path =3D ext4_find_extent(inode, EXT_DATA_LBLK, NULL, EXT4_EX_NOCACHE); ex =3D path->p_ext; =20 for (int i =3D 0; i < param->nr_exp_ext; i++) { struct kunit_ext_state exp_ext =3D param->exp_ext_state[i]; + bool es_check_needed =3D param->type !=3D TEST_SPLIT_CONVERT; + struct extent_status es; + int contains_ex, ex_end, es_end, es_pblk; =20 KUNIT_EXPECT_EQ(test, exp_ext.ex_lblk, le32_to_cpu(ex->ee_block)); @@ -419,6 +409,33 @@ static void test_split_convert(struct kunit *test) ext4_ext_get_actual_len(ex)); KUNIT_EXPECT_EQ(test, exp_ext.is_unwrit, ext4_ext_is_unwritten(ex)); + /* + * Confirm extent cache is in sync. Note that es cache can be + * merged even when on-disk extents are not so take that into + * account. + * + * Also, ext4_split_convert_extents() forces EXT4_EX_NOCACHE hence + * es status are ignored for that case. + */ + if (es_check_needed) { + ext4_es_lookup_extent(inode, le32_to_cpu(ex->ee_block), + NULL, &es, NULL); + + ex_end =3D exp_ext.ex_lblk + exp_ext.ex_len; + es_end =3D es.es_lblk + es.es_len; + contains_ex =3D es.es_lblk <=3D exp_ext.ex_lblk && + es_end >=3D ex_end; + es_pblk =3D ext4_es_pblock(&es) + + (exp_ext.ex_lblk - es.es_lblk); + + KUNIT_EXPECT_EQ(test, contains_ex, 1); + KUNIT_EXPECT_EQ(test, ext4_ext_pblock(ex), es_pblk); + KUNIT_EXPECT_EQ(test, 1, + (exp_ext.is_unwrit && + ext4_es_is_unwritten(&es)) || + (!exp_ext.is_unwrit && + ext4_es_is_written(&es))); + } =20 /* Only printed on failure */ kunit_log(KERN_INFO, test, @@ -429,6 +446,12 @@ static void test_split_convert(struct kunit *test) le32_to_cpu(ex->ee_block), ext4_ext_get_actual_len(ex), ext4_ext_is_unwritten(ex)); + if (es_check_needed) + kunit_log( + KERN_INFO, test, + "# [extent %d] es: lblk:%d len:%d pblk:%lld type:0x%x\n", + i, es.es_lblk, es.es_len, ext4_es_pblock(&es), + ext4_es_type(&es)); kunit_log(KERN_INFO, test, "------------------\n"); =20 ex =3D ex + 1; diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c index 08aa36854c11..16326d7f09b9 100644 --- a/fs/ext4/extents.c +++ b/fs/ext4/extents.c @@ -3145,8 +3145,6 @@ static void ext4_zeroout_es(struct inode *inode, stru= ct ext4_extent *ex) ext4_fsblk_t ee_pblock; unsigned int ee_len; =20 - KUNIT_STATIC_STUB_REDIRECT(ext4_zeroout_es, inode, ex); - ee_block =3D le32_to_cpu(ex->ee_block); ee_len =3D ext4_ext_get_actual_len(ex); ee_pblock =3D ext4_ext_pblock(ex); diff --git a/fs/ext4/extents_status.c b/fs/ext4/extents_status.c index 095ccb7ba4ba..a1538bac51c6 100644 --- a/fs/ext4/extents_status.c +++ b/fs/ext4/extents_status.c @@ -916,9 +916,6 @@ void ext4_es_insert_extent(struct inode *inode, ext4_lb= lk_t lblk, struct pending_reservation *pr =3D NULL; bool revise_pending =3D false; =20 - KUNIT_STATIC_STUB_REDIRECT(ext4_es_insert_extent, inode, lblk, len, - pblk, status, delalloc_reserve_used); - if (EXT4_SB(inode->i_sb)->s_mount_state & EXT4_FC_REPLAY) return; =20 @@ -1631,8 +1628,6 @@ void ext4_es_remove_extent(struct inode *inode, ext4_= lblk_t lblk, int reserved =3D 0; struct extent_status *es =3D NULL; =20 - KUNIT_STATIC_STUB_REDIRECT(ext4_es_remove_extent, inode, lblk, len); - if (EXT4_SB(inode->i_sb)->s_mount_state & EXT4_FC_REPLAY) return; =20 --=20 2.52.0 From nobody Sat Feb 7 21:53:15 2026 Received: from mx0a-001b2d01.pphosted.com (mx0a-001b2d01.pphosted.com [148.163.156.1]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 56D86395275; Fri, 23 Jan 2026 06:26:05 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=148.163.156.1 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769149576; cv=none; b=XKggEcnZw3pxWNlvohU3pbrkTtIT0sEU5IVSRoBauRp48OjZKJ0qN8I5obS+o2no006n0dzOx9nEtARYb6A5PZeZx47mFP87aKhHCuWRUYV8UcW5sjCl4FIntMCJ5q7gy+rewAxpOOknpvSqOVhfLGa7MtE7radczsCBOhuMxPo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769149576; c=relaxed/simple; bh=DGEjxdJl6LH0VdK1UgAhBH2d9x0bUVT0Z1AeZwFYQHo=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Fk3abP5377PAAQo8LYWMeMgqetuIrfBwCnuskgFrNWfsDiU1i3OTdNJUDUZxXrJd11ze3mv1odr1fJaN4yYW6dqJyYujLD4F7X96trEJvrjZyH7XK3V0dsAud/mxm2/ZRvacUcOGBAnqKN/fcABuS+YdCRU2+1QP1EF8OeiKr5U= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.ibm.com; spf=pass smtp.mailfrom=linux.ibm.com; dkim=pass (2048-bit key) header.d=ibm.com header.i=@ibm.com header.b=kiBDprsW; arc=none smtp.client-ip=148.163.156.1 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.ibm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.ibm.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=ibm.com header.i=@ibm.com header.b="kiBDprsW" Received: from pps.filterd (m0353729.ppops.net [127.0.0.1]) by mx0a-001b2d01.pphosted.com (8.18.1.2/8.18.1.2) with ESMTP id 60N1EdLA006245; Fri, 23 Jan 2026 06:25:56 GMT DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=ibm.com; h=cc :content-transfer-encoding:date:from:in-reply-to:message-id :mime-version:references:subject:to; s=pp1; bh=8/YfEzgiDEZV1CDSv O5rfWZx+fuiJ1qSRWERCJ/BGBA=; b=kiBDprsWhHlUYci5ckAT3siMdgj9Huaja 61HGqiGncsD8RoCV7jPIoLNeCmZVRx61J+15fBQWbYVUUKEJMwtJzIWQ+7s9VtBH i5HX/Le6JTGRBLvtBN+QkEy7Umj880OzcZvHe0WV3ujWcj9DpK742ZWb/xJn4aBb 80newayymhYFHhtVcAWIKBY3eEyeDenNI7XbWElWK7PJGfiz7Er5iTZ3fqpUlCTi aVVXiXCrk+g3QJ7kRth0ZHpkjBr/VDGZM1yxVORSQ1zSReLtPSsolVs5lJ2jcqkp nW8+4RKfFqsXeuGzLZA+K4sgjTqYc30E+gsFmaUeqFvg4Ywuf91OQ== Received: from pps.reinject (localhost [127.0.0.1]) by mx0a-001b2d01.pphosted.com (PPS) with ESMTPS id 4br23sec82-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Fri, 23 Jan 2026 06:25:55 +0000 (GMT) Received: from m0353729.ppops.net (m0353729.ppops.net [127.0.0.1]) by pps.reinject (8.18.1.12/8.18.0.8) with ESMTP id 60N6CPQ7022476; Fri, 23 Jan 2026 06:25:55 GMT Received: from ppma21.wdc07v.mail.ibm.com (5b.69.3da9.ip4.static.sl-reverse.com [169.61.105.91]) by mx0a-001b2d01.pphosted.com (PPS) with ESMTPS id 4br23sec7y-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Fri, 23 Jan 2026 06:25:55 +0000 (GMT) Received: from pps.filterd (ppma21.wdc07v.mail.ibm.com [127.0.0.1]) by ppma21.wdc07v.mail.ibm.com (8.18.1.2/8.18.1.2) with ESMTP id 60N3dieV027334; Fri, 23 Jan 2026 06:25:53 GMT Received: from smtprelay06.fra02v.mail.ibm.com ([9.218.2.230]) by ppma21.wdc07v.mail.ibm.com (PPS) with ESMTPS id 4brnrnfg54-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Fri, 23 Jan 2026 06:25:53 +0000 Received: from smtpav02.fra02v.mail.ibm.com (smtpav02.fra02v.mail.ibm.com [10.20.54.101]) by smtprelay06.fra02v.mail.ibm.com (8.14.9/8.14.9/NCO v10.0) with ESMTP id 60N6PpWL20840732 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK); Fri, 23 Jan 2026 06:25:51 GMT Received: from smtpav02.fra02v.mail.ibm.com (unknown [127.0.0.1]) by IMSVA (Postfix) with ESMTP id AB42A20043; Fri, 23 Jan 2026 06:25:51 +0000 (GMT) Received: from smtpav02.fra02v.mail.ibm.com (unknown [127.0.0.1]) by IMSVA (Postfix) with ESMTP id B906720040; Fri, 23 Jan 2026 06:25:49 +0000 (GMT) Received: from li-dc0c254c-257c-11b2-a85c-98b6c1322444.ibm.com (unknown [9.39.26.206]) by smtpav02.fra02v.mail.ibm.com (Postfix) with ESMTP; Fri, 23 Jan 2026 06:25:49 +0000 (GMT) From: Ojaswin Mujoo To: linux-ext4@vger.kernel.org, "Theodore Ts'o" Cc: Ritesh Harjani , Zhang Yi , Jan Kara , libaokun1@huawei.com, linux-kernel@vger.kernel.org Subject: [PATCH v4 4/8] ext4: propagate flags to convert_initialized_extent() Date: Fri, 23 Jan 2026 11:55:35 +0530 Message-ID: <07008fbb14db727fddcaf4c30e2346c49f6c8fe0.1769149131.git.ojaswin@linux.ibm.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-TM-AS-GCONF: 00 X-Proofpoint-GUID: q6NIIIHeHljo8CbmaaVCjKn666foaKpS X-Proofpoint-Spam-Details-Enc: AW1haW4tMjYwMTIzMDA0NiBTYWx0ZWRfXxaNlTKAnMI4u FCXc4jCP4cNUJ788teBWfOEYNSLFo9qf1qlhDId9oUCccBYg/QdzMGBMa7YpiEwt45wdqIJm5PB WITtbuI3hG+jkuonR9AWCV/8VxO1S6Ij02qjBeUmqfoUGEf3SZGkdQKMNzsuI8TqcCnhdI7J1Uf GwhYTuOoyGHzVk9o1p+JvBc72nuwIMhR0cbkv7afzTKzaBs1NYWF4NUtfq8XyUBZTjI2NIil/4L BSKIHS3a3vGYX+svKNSuicwbsGJlRhM64pbspHSNjCfjpudximx3L/1j2UYGgj5yWiiasA8SZKT pEo2kL/gAaikmcv60vkdyjYBWvqJjlHT+4AlyGOs+gXdpFAxDuHksqsTnAgA7T/lPM1DXHKAQ1l K0yT4cBzm2l7BKRj03dsoZySSRQAHSwT6RybVLV7Lv6sGkfE+RFP8CdT3Dd5hiT1fux9xXCYJ2g yvxbYZ5tzKOy4PR+tRQ== X-Authority-Analysis: v=2.4 cv=J9SnLQnS c=1 sm=1 tr=0 ts=69731473 cx=c_pps a=GFwsV6G8L6GxiO2Y/PsHdQ==:117 a=GFwsV6G8L6GxiO2Y/PsHdQ==:17 a=vUbySO9Y5rIA:10 a=VkNPw1HP01LnGYTKEx00:22 a=i0EeH86SAAAA:8 a=VnNF1IyMAAAA:8 a=oIlp7qZINqeTlFEOi2UA:9 X-Proofpoint-ORIG-GUID: 1f0L7Cq82ESU_bb4j6HB_m1VTBvSSOR7 X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1121,Hydra:6.1.20,FMLib:17.12.100.49 definitions=2026-01-22_06,2026-01-22_02,2025-10-01_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 priorityscore=1501 impostorscore=0 adultscore=0 suspectscore=0 spamscore=0 lowpriorityscore=0 malwarescore=0 clxscore=1015 bulkscore=0 phishscore=0 classifier=typeunknown authscore=0 authtc= authcc= route=outbound adjust=0 reason=mlx scancount=1 engine=8.19.0-2601150000 definitions=main-2601230046 Content-Type: text/plain; charset="utf-8" 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 Reviewed-by: Jan Kara Signed-off-by: Ojaswin Mujoo --- 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 16326d7f09b9..2747af91e78e 100644 --- a/fs/ext4/extents.c +++ b/fs/ext4/extents.c @@ -3840,6 +3840,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; @@ -3865,11 +3866,11 @@ convert_initialized_extent(handle_t *handle, struct= inode *inode, =20 if (ee_block !=3D map->m_lblk || ee_len > map->m_len) { path =3D ext4_split_convert_extents(handle, inode, map, path, - EXT4_GET_BLOCKS_CONVERT_UNWRITTEN, NULL); + flags, NULL); if (IS_ERR(path)) return path; =20 - path =3D ext4_find_extent(inode, map->m_lblk, path, 0); + path =3D ext4_find_extent(inode, map->m_lblk, path, flags); if (IS_ERR(path)) return path; depth =3D ext_depth(inode); @@ -4259,7 +4260,7 @@ int ext4_ext_map_blocks(handle_t *handle, struct inod= e *inode, if ((!ext4_ext_is_unwritten(ex)) && (flags & EXT4_GET_BLOCKS_CONVERT_UNWRITTEN)) { path =3D convert_initialized_extent(handle, - inode, map, path, &allocated); + inode, map, path, flags, &allocated); if (IS_ERR(path)) err =3D PTR_ERR(path); goto out; --=20 2.52.0 From nobody Sat Feb 7 21:53:15 2026 Received: from mx0a-001b2d01.pphosted.com (mx0a-001b2d01.pphosted.com [148.163.156.1]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E664436AB47; Fri, 23 Jan 2026 06:26:07 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=148.163.156.1 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769149576; cv=none; b=trcxkwpj0gt1q8YlXUXJ/xA2caTLDxKmE5I7bQAwm+pzrDqsm+Y8vUFF5MqWcIdgTzpqFpEVoHUJWStscAFDT2tZgz83id9nTCciSEXB/hRHPwyhXXojrXyGFlnvRWLskaynOGFmaHwNR90PHuShMgN+GEqTLQa/3aLtTd66Duc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769149576; c=relaxed/simple; bh=Ne9Og/NKADuc//8Ut+/1aozADufGWbHaGdFLUgehmNo=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=G8HgQnRx/HgVnbkEgZMB5l+ktN6YHS5EdIpFoLm6PWWGi5ztjXQ+Dr96aFF9ClcXD1UHq+EYmJuyM+x0UBWTBba0CxJk1gTEYtkw8bSNiHxJAwAlkd+swe8AVS9SfVYilMbiZUWzSxxPGc5el9+z8QbrDlGWc9UkN93Ua/dB+rE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.ibm.com; spf=pass smtp.mailfrom=linux.ibm.com; dkim=pass (2048-bit key) header.d=ibm.com header.i=@ibm.com header.b=jYkn8eSj; arc=none smtp.client-ip=148.163.156.1 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.ibm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.ibm.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=ibm.com header.i=@ibm.com header.b="jYkn8eSj" Received: from pps.filterd (m0356517.ppops.net [127.0.0.1]) by mx0a-001b2d01.pphosted.com (8.18.1.2/8.18.1.2) with ESMTP id 60N010GZ020784; Fri, 23 Jan 2026 06:25:57 GMT DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=ibm.com; h=cc :content-transfer-encoding:date:from:in-reply-to:message-id :mime-version:references:subject:to; s=pp1; bh=GkGcSgiruMdMKbeXa c3R3h1XOdGbXjApmqmEjRTvb6I=; b=jYkn8eSjs1HXyzisjW7B3vmDAMBN1CiFA pMcTKmY25AaQbewLLaCFdFsVZagUXWessPzbMfFFixHb7k+rZzMkC7hvhtKVk7F1 2FOvS02Ug1g5opLepTlYSvTs4jSSbOAYZ7vwO0CHF5dRJHZrJdqLgSxwwdKE9NsI QPmXV4crdpeoV40Yz3yIwBkCLmUFaBxmta+UexOdV9TvMAU+UzKeJmZ0WOIHeh+1 /y3fHtyZ3EZ4olsZWb6s/ozHjh90HRYdmNH+V7EpvBN3DgixxJMkG+l4AsH9+rkL Cst4RjjzAolY8Sf/WTknQpphijQwnlXzSbG7QVRIj/LDhnD7PMVCA== Received: from pps.reinject (localhost [127.0.0.1]) by mx0a-001b2d01.pphosted.com (PPS) with ESMTPS id 4br256ea95-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Fri, 23 Jan 2026 06:25:57 +0000 (GMT) Received: from m0356517.ppops.net (m0356517.ppops.net [127.0.0.1]) by pps.reinject (8.18.1.12/8.18.0.8) with ESMTP id 60N6Pffk022007; Fri, 23 Jan 2026 06:25:56 GMT Received: from ppma12.dal12v.mail.ibm.com (dc.9e.1632.ip4.static.sl-reverse.com [50.22.158.220]) by mx0a-001b2d01.pphosted.com (PPS) with ESMTPS id 4br256ea92-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Fri, 23 Jan 2026 06:25:56 +0000 (GMT) Received: from pps.filterd (ppma12.dal12v.mail.ibm.com [127.0.0.1]) by ppma12.dal12v.mail.ibm.com (8.18.1.2/8.18.1.2) with ESMTP id 60N44MUX024652; Fri, 23 Jan 2026 06:25:55 GMT Received: from smtprelay05.fra02v.mail.ibm.com ([9.218.2.225]) by ppma12.dal12v.mail.ibm.com (PPS) with ESMTPS id 4brxas5tgb-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Fri, 23 Jan 2026 06:25:55 +0000 Received: from smtpav02.fra02v.mail.ibm.com (smtpav02.fra02v.mail.ibm.com [10.20.54.101]) by smtprelay05.fra02v.mail.ibm.com (8.14.9/8.14.9/NCO v10.0) with ESMTP id 60N6PsB744499428 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK); Fri, 23 Jan 2026 06:25:54 GMT Received: from smtpav02.fra02v.mail.ibm.com (unknown [127.0.0.1]) by IMSVA (Postfix) with ESMTP id 06B5B2004B; Fri, 23 Jan 2026 06:25:54 +0000 (GMT) Received: from smtpav02.fra02v.mail.ibm.com (unknown [127.0.0.1]) by IMSVA (Postfix) with ESMTP id 1A23120040; Fri, 23 Jan 2026 06:25:52 +0000 (GMT) Received: from li-dc0c254c-257c-11b2-a85c-98b6c1322444.ibm.com (unknown [9.39.26.206]) by smtpav02.fra02v.mail.ibm.com (Postfix) with ESMTP; Fri, 23 Jan 2026 06:25:51 +0000 (GMT) From: Ojaswin Mujoo To: linux-ext4@vger.kernel.org, "Theodore Ts'o" Cc: Ritesh Harjani , Zhang Yi , Jan Kara , libaokun1@huawei.com, linux-kernel@vger.kernel.org Subject: [PATCH v4 5/8] ext4: propagate flags to ext4_convert_unwritten_extents_endio() Date: Fri, 23 Jan 2026 11:55:36 +0530 Message-ID: <7c2139e0ad32c49c19b194f72219e15d613de284.1769149131.git.ojaswin@linux.ibm.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-TM-AS-GCONF: 00 X-Proofpoint-Spam-Details-Enc: AW1haW4tMjYwMTIzMDA0NiBTYWx0ZWRfX3HSnZkIiFcPN VjWTIg3uQCAhlLj66XFYIbv2qRFP7a84H5BYMUjiBcM0s8bLPU+bqV2N8a5BIkXQd7Qldexi8uP yggP5qR+gPuIvY76Rto+6cXFlkLH6uZgS4rI6ku3o2MGIqIXkr6ti0UWEGobRM7KueOH0yB0vGY Y0DPN6iS2OevV9idvA8u2gQnhC3I6eni+erM3Ru+NzmohKP2qUOBiTVFGExH3HPyAe33oguOCJB TgEwzvwaVxqnyOzKM/fp/YZL9Uw2WFi844mJLxaJyACgVPheNqJGad2tERRq3s+OMqob0L9+48k P/zUR+dRphuIw7080zYNfxM61w4CYIGb5X+VVVtoHD65+rG0fkrIXGVPn3VVlIKmtDKd2Qj2BeX lUzV5RLu4yth8u8jR5/KCKUtRihoJbojQXl2Jcaj0g515+TEf2487d9MKom/p44l9nOmXs6n59F e+7As0ADHFTUI/ePJvA== X-Authority-Analysis: v=2.4 cv=BpSQAIX5 c=1 sm=1 tr=0 ts=69731475 cx=c_pps a=bLidbwmWQ0KltjZqbj+ezA==:117 a=bLidbwmWQ0KltjZqbj+ezA==:17 a=vUbySO9Y5rIA:10 a=VkNPw1HP01LnGYTKEx00:22 a=i0EeH86SAAAA:8 a=VnNF1IyMAAAA:8 a=s5sEB8iLFlBga5WgvCgA:9 X-Proofpoint-GUID: 4SvAm28hZ7VGp2N7zhFO-WINByT_RXIH X-Proofpoint-ORIG-GUID: nF1wgPl9jf8iO_mmYgpuoSYKDj9dqvOG X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1121,Hydra:6.1.20,FMLib:17.12.100.49 definitions=2026-01-22_06,2026-01-22_02,2025-10-01_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 spamscore=0 bulkscore=0 clxscore=1015 adultscore=0 phishscore=0 malwarescore=0 impostorscore=0 suspectscore=0 priorityscore=1501 lowpriorityscore=0 classifier=typeunknown authscore=0 authtc= authcc= route=outbound adjust=0 reason=mlx scancount=1 engine=8.19.0-2601150000 definitions=main-2601230046 Content-Type: text/plain; charset="utf-8" Currently, callers like ext4_convert_unwritten_extents() pass EXT4_EX_NOCACHE flag to avoid caching extents however this is not respected by ext4_convert_unwritten_extents_endio(). 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, since the caller already passes METADATA_NOFAIL and CONVERT flags we don't need to explicitly pass it anymore. Reviewed-by: Jan Kara Reviewed-by: Zhang Yi Signed-off-by: Ojaswin Mujoo --- fs/ext4/extents.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c index 2747af91e78e..20939b5526b8 100644 --- a/fs/ext4/extents.c +++ b/fs/ext4/extents.c @@ -3780,7 +3780,7 @@ static struct ext4_ext_path *ext4_split_convert_exten= ts(handle_t *handle, static struct ext4_ext_path * ext4_convert_unwritten_extents_endio(handle_t *handle, struct inode *inode, struct ext4_map_blocks *map, - struct ext4_ext_path *path) + struct ext4_ext_path *path, int flags) { struct ext4_extent *ex; ext4_lblk_t ee_block; @@ -3797,15 +3797,12 @@ ext4_convert_unwritten_extents_endio(handle_t *hand= le, struct inode *inode, (unsigned long long)ee_block, ee_len); =20 if (ee_block !=3D map->m_lblk || ee_len > map->m_len) { - int flags =3D EXT4_GET_BLOCKS_CONVERT | - EXT4_GET_BLOCKS_METADATA_NOFAIL; - path =3D ext4_split_convert_extents(handle, inode, map, path, flags, NULL); if (IS_ERR(path)) return path; =20 - path =3D ext4_find_extent(inode, map->m_lblk, path, 0); + path =3D ext4_find_extent(inode, map->m_lblk, path, flags); if (IS_ERR(path)) return path; depth =3D ext_depth(inode); @@ -3938,7 +3935,7 @@ ext4_ext_handle_unwritten_extents(handle_t *handle, s= truct inode *inode, /* IO end_io complete, convert the filled extent to written */ if (flags & EXT4_GET_BLOCKS_CONVERT) { path =3D ext4_convert_unwritten_extents_endio(handle, inode, - map, path); + map, path, flags); if (IS_ERR(path)) return path; ext4_update_inode_fsync_trans(handle, inode, 1); --=20 2.52.0 From nobody Sat Feb 7 21:53:15 2026 Received: from mx0b-001b2d01.pphosted.com (mx0b-001b2d01.pphosted.com [148.163.158.5]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5B3242777E0; Fri, 23 Jan 2026 06:26:10 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=148.163.158.5 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769149575; cv=none; b=nfgT/o3+dyc++HwxSS0/Owf1ikZjf53kIVis48AMX+5qau9rWT3dNpcLbB2RAkHBxHp1/0uvw3fvW6G2ALDRAFp0jJ9SbvsXNZgzfEfuDWSXxaeO5E/GK+Zq1nt93/66YHij8oPn0jVpZFezLohp+ZeopR2tSF9MnSGw8RPvhxg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769149575; c=relaxed/simple; bh=b5yHFLMxSTUDF0zKICzBtN0DhCs429pWAqwiP5BYFQs=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=hZuw/SOa8PmGw+wgHkz60XS2bhPqGsa/oOnBqQ86u09sl7S9RnPvzu5nO4AymketYeCl7av/6mkhVI8oCLmqROC69PUuPAdQfjdMx+aNes7wV+4ClFoKNcFwE4gY0w+z7wtP/WLEU+2V5ieZnkUu0XkwJ5sLWg5j0LZcYvrvHWY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.ibm.com; spf=pass smtp.mailfrom=linux.ibm.com; dkim=pass (2048-bit key) header.d=ibm.com header.i=@ibm.com header.b=Yh1T4vmV; arc=none smtp.client-ip=148.163.158.5 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.ibm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.ibm.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=ibm.com header.i=@ibm.com header.b="Yh1T4vmV" Received: from pps.filterd (m0360072.ppops.net [127.0.0.1]) by mx0a-001b2d01.pphosted.com (8.18.1.2/8.18.1.2) with ESMTP id 60MJwLlr022521; Fri, 23 Jan 2026 06:25:59 GMT DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=ibm.com; h=cc :content-transfer-encoding:date:from:in-reply-to:message-id :mime-version:references:subject:to; s=pp1; bh=oBYp4g9VFRxsKfQ97 gvWhqo5AgdV7CuDH5r0MUo4/E0=; b=Yh1T4vmVh2M2t0n3+/y8Cbp1tp/PyGwti GS6M+s/XWDf8ibqmy+Q3oD6NORWB8MLGZKGmI4mST+3CBDsPcRh8GjNmM6g33BcV I9OdrJWMipSlA+soAoxOoclcikXtqTlj3oBY2zcHL+MuMQCJpnkG/hgQ1BBbf8L3 L0nsiqnOQxgfio0BA0Mnsl2EMrk/zCMbhdFV5iYwd/6l7J8mV/a7JN/chacwE8IO Jt1vb5VWf+4zt8LLrwf1UDguPJOiPKWxWcaeUkFS/88KkqZGya/bmK741yZDlCoM pt6KN4jGW13usIgY2X56BsMmXl2h0cSG93WZ7JACLCLdwkTdkgv6A== Received: from pps.reinject (localhost [127.0.0.1]) by mx0a-001b2d01.pphosted.com (PPS) with ESMTPS id 4bt612h1cf-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Fri, 23 Jan 2026 06:25:59 +0000 (GMT) Received: from m0360072.ppops.net (m0360072.ppops.net [127.0.0.1]) by pps.reinject (8.18.1.12/8.18.0.8) with ESMTP id 60N6Oe5J003003; Fri, 23 Jan 2026 06:25:59 GMT Received: from ppma21.wdc07v.mail.ibm.com (5b.69.3da9.ip4.static.sl-reverse.com [169.61.105.91]) by mx0a-001b2d01.pphosted.com (PPS) with ESMTPS id 4bt612h1c2-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Fri, 23 Jan 2026 06:25:59 +0000 (GMT) Received: from pps.filterd (ppma21.wdc07v.mail.ibm.com [127.0.0.1]) by ppma21.wdc07v.mail.ibm.com (8.18.1.2/8.18.1.2) with ESMTP id 60N3vNtL027285; Fri, 23 Jan 2026 06:25:58 GMT Received: from smtprelay02.fra02v.mail.ibm.com ([9.218.2.226]) by ppma21.wdc07v.mail.ibm.com (PPS) with ESMTPS id 4brnrnfg60-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Fri, 23 Jan 2026 06:25:58 +0000 Received: from smtpav02.fra02v.mail.ibm.com (smtpav02.fra02v.mail.ibm.com [10.20.54.101]) by smtprelay02.fra02v.mail.ibm.com (8.14.9/8.14.9/NCO v10.0) with ESMTP id 60N6PuOR50331984 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK); Fri, 23 Jan 2026 06:25:56 GMT Received: from smtpav02.fra02v.mail.ibm.com (unknown [127.0.0.1]) by IMSVA (Postfix) with ESMTP id 4D05620043; Fri, 23 Jan 2026 06:25:56 +0000 (GMT) Received: from smtpav02.fra02v.mail.ibm.com (unknown [127.0.0.1]) by IMSVA (Postfix) with ESMTP id 7202E20040; Fri, 23 Jan 2026 06:25:54 +0000 (GMT) Received: from li-dc0c254c-257c-11b2-a85c-98b6c1322444.ibm.com (unknown [9.39.26.206]) by smtpav02.fra02v.mail.ibm.com (Postfix) with ESMTP; Fri, 23 Jan 2026 06:25:54 +0000 (GMT) From: Ojaswin Mujoo To: linux-ext4@vger.kernel.org, "Theodore Ts'o" Cc: Ritesh Harjani , Zhang Yi , Jan Kara , libaokun1@huawei.com, linux-kernel@vger.kernel.org Subject: [PATCH v4 6/8] ext4: Refactor zeroout path and handle all cases Date: Fri, 23 Jan 2026 11:55:37 +0530 Message-ID: X-Mailer: git-send-email 2.52.0 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-TM-AS-GCONF: 00 X-Proofpoint-GUID: yG2ANgIBcmw-4f-VigA_Z8mhvpcPQ7Yc X-Authority-Analysis: v=2.4 cv=LaIxKzfi c=1 sm=1 tr=0 ts=69731477 cx=c_pps a=GFwsV6G8L6GxiO2Y/PsHdQ==:117 a=GFwsV6G8L6GxiO2Y/PsHdQ==:17 a=vUbySO9Y5rIA:10 a=VkNPw1HP01LnGYTKEx00:22 a=i0EeH86SAAAA:8 a=VnNF1IyMAAAA:8 a=CHfUTuQ1SsgL1pUXN6wA:9 X-Proofpoint-ORIG-GUID: kvdixK4f0pOZKjS8clctBWpKzV74EwlJ X-Proofpoint-Spam-Details-Enc: AW1haW4tMjYwMTIzMDA0NiBTYWx0ZWRfX8ZHbbF1rHrSe oYo6nUlqq4OfSsR4dA6gg5DFbuLEB+ljE55BBORavPciYSEw65qMCmqyupEg/6zeIgwx9/3O/TA zra+qVXCdLUxO8679IBy+7Bl7F8qOu9xXrb7fs+kY2U+zhIwJHJ2zui38tHAP79mFcInlI8nPph Oh8A1eDItDUJruNo82sAfofIsaFnDb1fu/Zu8w+1fduiHtOujwcLttS9/6hqX1ZinPnSM8jfpT9 GDArZBDYcJHTiq1+/aFiF9eXK0kUpIFwVGJnVkjfrSZsPruJcE9gXrF6142ohO6WaDPAPPH3QHZ RIHeIREUqaElp+bwxihk7NQ5hvMWBpkkWN4K6FUfZxpCTFaLnlqw9QvrH7xBvi5UKkIGlHroOH4 hDaGifaJpLEZrurd2oS2KJf2TWKShe3WRzlDhuKxYAnmh9Tdimg4DMMIIqPVfJOIt2fGhEKDNhV Z9nOA+i7F5ddrb/2RPw== X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1121,Hydra:6.1.20,FMLib:17.12.100.49 definitions=2026-01-22_06,2026-01-22_02,2025-10-01_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 bulkscore=0 adultscore=0 phishscore=0 priorityscore=1501 lowpriorityscore=0 suspectscore=0 clxscore=1015 impostorscore=0 spamscore=0 malwarescore=0 classifier=typeunknown authscore=0 authtc= authcc= route=outbound adjust=0 reason=mlx scancount=1 engine=8.19.0-2601150000 definitions=main-2601230046 Content-Type: text/plain; charset="utf-8" Currently, zeroout is used as a fallback in case we fail to split/convert extents in the "traditional" modify-the-extent-tree way. This is essential to mitigate failures in critical paths like extent splitting during endio. However, the logic is very messy and not easy to follow. Further, the fragile use of various flags has made it prone to errors. Refactor zeroout out logic by moving it up to ext4_split_extents(). Further, zeroout correctly based on the type of conversion we want, ie: - unwritten to written: Zeroout everything around the mapped range. - written to unwritten: Zeroout only the mapped range. Also, ext4_ext_convert_to_initialized() now passes EXT4_GET_BLOCKS_CONVERT to make the intention clear. Reviewed-by: Zhang Yi Reviewed-by: Jan Kara Signed-off-by: Ojaswin Mujoo --- fs/ext4/extents.c | 283 ++++++++++++++++++++++++++++++---------------- 1 file changed, 185 insertions(+), 98 deletions(-) diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c index 20939b5526b8..8d709bfd299b 100644 --- a/fs/ext4/extents.c +++ b/fs/ext4/extents.c @@ -44,14 +44,6 @@ #define EXT4_EXT_MARK_UNWRIT1 0x2 /* mark first half unwritten */ #define EXT4_EXT_MARK_UNWRIT2 0x4 /* mark second half unwritten */ =20 -/* 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) { @@ -3189,7 +3181,8 @@ static int ext4_ext_zeroout(struct inode *inode, stru= ct ext4_extent *ex) * a> the extent are splitted into two extent. * b> split is not needed, and just mark the extent. * - * Return an extent path pointer on success, or an error pointer on failur= e. + * Return an extent path pointer on success, or an error pointer on failur= e. On + * failure, the extent is restored to original state. */ static struct ext4_ext_path *ext4_split_extent_at(handle_t *handle, struct inode *inode, @@ -3199,14 +3192,10 @@ static struct ext4_ext_path *ext4_split_extent_at(h= andle_t *handle, { ext4_fsblk_t newblock; ext4_lblk_t ee_block; - struct ext4_extent *ex, newex, orig_ex, zero_ex; + struct ext4_extent *ex, newex, orig_ex; struct ext4_extent *ex2 =3D NULL; unsigned int ee_len, depth; - int err =3D 0; - - BUG_ON((split_flag & EXT4_EXT_DATA_VALID1) =3D=3D EXT4_EXT_DATA_VALID1); - BUG_ON((split_flag & EXT4_EXT_DATA_VALID1) && - (split_flag & EXT4_EXT_DATA_VALID2)); + int err =3D 0, insert_err =3D 0; =20 /* Do not cache extents that are in the process of being modified. */ flags |=3D EXT4_EX_NOCACHE; @@ -3272,11 +3261,10 @@ static struct ext4_ext_path *ext4_split_extent_at(h= andle_t *handle, =20 path =3D ext4_ext_insert_extent(handle, inode, path, &newex, flags); if (!IS_ERR(path)) - goto out; + return path; =20 - err =3D PTR_ERR(path); - if (err !=3D -ENOSPC && err !=3D -EDQUOT && err !=3D -ENOMEM) - goto out_path; + insert_err =3D PTR_ERR(path); + err =3D 0; =20 /* * Get a new path to try to zeroout or fix the extent length. @@ -3292,72 +3280,130 @@ static struct ext4_ext_path *ext4_split_extent_at(= handle_t *handle, split, PTR_ERR(path)); goto out_path; } + + err =3D ext4_ext_get_access(handle, inode, path + depth); + if (err) + goto out; + depth =3D ext_depth(inode); ex =3D path[depth].p_ext; =20 - if (EXT4_EXT_MAY_ZEROOUT & split_flag) { - if (split_flag & EXT4_EXT_DATA_VALID1) - memcpy(&zero_ex, ex2, sizeof(zero_ex)); - else if (split_flag & EXT4_EXT_DATA_VALID2) - memcpy(&zero_ex, ex, sizeof(zero_ex)); - else - memcpy(&zero_ex, &orig_ex, sizeof(zero_ex)); - ext4_ext_mark_initialized(&zero_ex); +fix_extent_len: + ex->ee_len =3D orig_ex.ee_len; + err =3D ext4_ext_dirty(handle, inode, path + path->p_depth); +out: + if (err || insert_err) { + ext4_free_ext_path(path); + path =3D err ? ERR_PTR(err) : ERR_PTR(insert_err); + } +out_path: + if (IS_ERR(path)) + /* Remove all remaining potentially stale extents. */ + ext4_es_remove_extent(inode, ee_block, ee_len); + ext4_ext_show_leaf(inode, path); + return path; +} =20 - err =3D ext4_ext_zeroout(inode, &zero_ex); - if (err) - goto fix_extent_len; +static int ext4_split_extent_zeroout(handle_t *handle, struct inode *inode, + struct ext4_ext_path *path, + struct ext4_map_blocks *map, int flags) +{ + struct ext4_extent *ex; + unsigned int ee_len, depth; + ext4_lblk_t ee_block; + uint64_t lblk, pblk, len; + int is_unwrit; + int err =3D 0; =20 + depth =3D ext_depth(inode); + ex =3D path[depth].p_ext; + ee_block =3D le32_to_cpu(ex->ee_block); + ee_len =3D ext4_ext_get_actual_len(ex); + is_unwrit =3D ext4_ext_is_unwritten(ex); + + if (flags & EXT4_GET_BLOCKS_CONVERT) { /* - * 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. + * EXT4_GET_BLOCKS_CONVERT: Caller wants the range specified by + * map to be initialized. Zeroout everything except the map + * range. */ - 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; + + loff_t map_end =3D (loff_t) map->m_lblk + map->m_len; + loff_t ex_end =3D (loff_t) ee_block + ee_len; + + if (!is_unwrit) + /* Shouldn't happen. Just exit */ + return -EINVAL; + + /* zeroout left */ + if (map->m_lblk > ee_block) { + lblk =3D ee_block; + len =3D map->m_lblk - ee_block; + pblk =3D ext4_ext_pblock(ex); + err =3D ext4_issue_zeroout(inode, lblk, pblk, len); + if (err) + /* ZEROOUT failed, just return original error */ + return err; } =20 - /* update the extent length and mark as initialized */ - ex->ee_len =3D cpu_to_le16(ee_len); - ext4_ext_try_to_merge(handle, inode, path, ex); - err =3D ext4_ext_dirty(handle, inode, path + path->p_depth); - if (!err) - /* update extent status tree */ - ext4_zeroout_es(inode, &zero_ex); + /* zeroout right */ + if (map_end < ex_end) { + lblk =3D map_end; + len =3D ex_end - map_end; + pblk =3D ext4_ext_pblock(ex) + (map_end - ee_block); + err =3D ext4_issue_zeroout(inode, lblk, pblk, len); + if (err) + /* ZEROOUT failed, just return original error */ + return err; + } + } else if (flags & EXT4_GET_BLOCKS_CONVERT_UNWRITTEN) { /* - * If we failed at this point, we don't know in which - * state the extent tree exactly is so don't try to fix - * length of the original extent as it may do even more - * damage. + * EXT4_GET_BLOCKS_CONVERT_UNWRITTEN: Caller wants the + * range specified by map to be marked unwritten. + * Zeroout the map range leaving rest as it is. */ - goto out; + + if (is_unwrit) + /* Shouldn't happen. Just exit */ + return -EINVAL; + + lblk =3D map->m_lblk; + len =3D map->m_len; + pblk =3D ext4_ext_pblock(ex) + (map->m_lblk - ee_block); + err =3D ext4_issue_zeroout(inode, lblk, pblk, len); + if (err) + /* ZEROOUT failed, just return original error */ + return err; + } else { + /* + * We no longer perform unwritten to unwritten splits in IO paths. + * Hence this should not happen. + */ + WARN_ON_ONCE(true); + return -EINVAL; } =20 -fix_extent_len: - ex->ee_len =3D orig_ex.ee_len; + err =3D ext4_ext_get_access(handle, inode, path + depth); + if (err) + return err; + + ext4_ext_mark_initialized(ex); + + ext4_ext_dirty(handle, inode, path + path->p_depth); + if (err) + return err; + /* - * Ignore ext4_ext_dirty return value since we are already in error path - * and err is a non-zero error code. + * The whole extent is initialized and stable now so it can be added to + * es cache */ - ext4_ext_dirty(handle, inode, path + path->p_depth); -out: - if (err) { - ext4_free_ext_path(path); - path =3D ERR_PTR(err); - } -out_path: - if (IS_ERR(path)) - /* Remove all remaining potentially stale extents. */ - ext4_es_remove_extent(inode, ee_block, ee_len); - ext4_ext_show_leaf(inode, path); - return path; + if (!(flags & EXT4_EX_NOCACHE)) + ext4_es_insert_extent(inode, le32_to_cpu(ex->ee_block), + ext4_ext_get_actual_len(ex), + ext4_ext_pblock(ex), + EXTENT_STATUS_WRITTEN, false); + + return 0; } =20 /* @@ -3378,11 +3424,13 @@ static struct ext4_ext_path *ext4_split_extent(hand= le_t *handle, int split_flag, int flags, unsigned int *allocated) { - ext4_lblk_t ee_block; + ext4_lblk_t ee_block, orig_ee_block; struct ext4_extent *ex; - unsigned int ee_len, depth; - int unwritten; - int split_flag1, flags1; + unsigned int ee_len, orig_ee_len, depth; + int unwritten, orig_unwritten; + int split_flag1 =3D 0, flags1 =3D 0; + int orig_err =3D 0; + int orig_flags =3D flags; =20 depth =3D ext_depth(inode); ex =3D path[depth].p_ext; @@ -3390,30 +3438,31 @@ static struct ext4_ext_path *ext4_split_extent(hand= le_t *handle, ee_len =3D ext4_ext_get_actual_len(ex); unwritten =3D ext4_ext_is_unwritten(ex); =20 + orig_ee_block =3D ee_block; + orig_ee_len =3D ee_len; + orig_unwritten =3D unwritten; + /* Do not cache extents that are in the process of being modified. */ flags |=3D EXT4_EX_NOCACHE; =20 if (map->m_lblk + map->m_len < ee_block + ee_len) { - split_flag1 =3D split_flag & EXT4_EXT_MAY_ZEROOUT; flags1 =3D flags | EXT4_GET_BLOCKS_SPLIT_NOMERGE; if (unwritten) split_flag1 |=3D EXT4_EXT_MARK_UNWRIT1 | EXT4_EXT_MARK_UNWRIT2; - if (split_flag & EXT4_EXT_DATA_VALID2) - split_flag1 |=3D map->m_lblk > ee_block ? - EXT4_EXT_DATA_PARTIAL_VALID1 : - EXT4_EXT_DATA_ENTIRE_VALID1; path =3D ext4_split_extent_at(handle, inode, path, map->m_lblk + map->m_len, split_flag1, flags1); if (IS_ERR(path)) - return path; + goto try_zeroout; + /* * Update path is required because previous ext4_split_extent_at * may result in split of original leaf or extent zeroout. */ path =3D ext4_find_extent(inode, map->m_lblk, path, flags); if (IS_ERR(path)) - return path; + goto try_zeroout; + depth =3D ext_depth(inode); ex =3D path[depth].p_ext; if (!ex) { @@ -3422,22 +3471,61 @@ static struct ext4_ext_path *ext4_split_extent(hand= le_t *handle, ext4_free_ext_path(path); return ERR_PTR(-EFSCORRUPTED); } - unwritten =3D ext4_ext_is_unwritten(ex); + + /* extent would have changed so update original values */ + orig_ee_block =3D le32_to_cpu(ex->ee_block); + orig_ee_len =3D ext4_ext_get_actual_len(ex); + orig_unwritten =3D ext4_ext_is_unwritten(ex); } =20 if (map->m_lblk >=3D ee_block) { - split_flag1 =3D split_flag & EXT4_EXT_DATA_VALID2; + split_flag1 =3D 0; if (unwritten) { split_flag1 |=3D EXT4_EXT_MARK_UNWRIT1; - split_flag1 |=3D split_flag & (EXT4_EXT_MAY_ZEROOUT | - EXT4_EXT_MARK_UNWRIT2); + split_flag1 |=3D split_flag & EXT4_EXT_MARK_UNWRIT2; } - path =3D ext4_split_extent_at(handle, inode, path, - map->m_lblk, split_flag1, flags); + path =3D ext4_split_extent_at(handle, inode, path, map->m_lblk, + split_flag1, flags); if (IS_ERR(path)) - return path; + goto try_zeroout; } =20 + goto success; + +try_zeroout: + /* + * There was an error in splitting the extent. So instead, just zeroout + * unwritten portions and convert it to initialized as a last resort. If + * there is any failure here we just return the original error + */ + + orig_err =3D PTR_ERR(path); + if (orig_err !=3D -ENOSPC && orig_err !=3D -EDQUOT && orig_err !=3D -ENOM= EM) + goto out_orig_err; + + /* we can't zeroout? just return the original err */ + if (!(split_flag & EXT4_EXT_MAY_ZEROOUT)) + goto out_orig_err; + + path =3D ext4_find_extent(inode, map->m_lblk, NULL, flags); + if (IS_ERR(path)) + goto out_orig_err; + + depth =3D ext_depth(inode); + ex =3D path[depth].p_ext; + ee_block =3D le32_to_cpu(ex->ee_block); + ee_len =3D ext4_ext_get_actual_len(ex); + unwritten =3D ext4_ext_is_unwritten(ex); + + /* extent to zeroout should have been unchanged but its not */ + if (WARN_ON(ee_block !=3D orig_ee_block || ee_len !=3D orig_ee_len || + unwritten !=3D orig_unwritten)) + goto out_free_path; + + if (ext4_split_extent_zeroout(handle, inode, path, map, orig_flags)) + goto out_free_path; + +success: if (allocated) { if (map->m_lblk + map->m_len > ee_block + ee_len) *allocated =3D ee_len - (map->m_lblk - ee_block); @@ -3446,6 +3534,12 @@ static struct ext4_ext_path *ext4_split_extent(handl= e_t *handle, } ext4_ext_show_leaf(inode, path); return path; + +out_free_path: + ext4_free_ext_path(path); +out_orig_err: + return ERR_PTR(orig_err); + } =20 /* @@ -3481,7 +3575,7 @@ ext4_ext_convert_to_initialized(handle_t *handle, str= uct inode *inode, ext4_lblk_t ee_block, eof_block; unsigned int ee_len, depth, map_len =3D map->m_len; int err =3D 0; - int split_flag =3D EXT4_EXT_DATA_VALID2; + int split_flag =3D 0; unsigned int max_zeroout =3D 0; =20 ext_debug(inode, "logical block %llu, max_blocks %u\n", @@ -3691,7 +3785,7 @@ ext4_ext_convert_to_initialized(handle_t *handle, str= uct inode *inode, =20 fallback: path =3D ext4_split_extent(handle, inode, path, &split_map, split_flag, - flags, NULL); + flags | EXT4_GET_BLOCKS_CONVERT, NULL); if (IS_ERR(path)) return path; out: @@ -3755,11 +3849,7 @@ static struct ext4_ext_path *ext4_split_convert_exte= nts(handle_t *handle, ee_block =3D le32_to_cpu(ex->ee_block); ee_len =3D ext4_ext_get_actual_len(ex); =20 - /* Convert to unwritten */ - if (flags & EXT4_GET_BLOCKS_CONVERT_UNWRITTEN) { - split_flag |=3D EXT4_EXT_DATA_ENTIRE_VALID1; - /* Split the existing unwritten extent */ - } else if (flags & (EXT4_GET_BLOCKS_UNWRIT_EXT | + if (flags & (EXT4_GET_BLOCKS_UNWRIT_EXT | EXT4_GET_BLOCKS_CONVERT)) { /* * It is safe to convert extent to initialized via explicit @@ -3768,9 +3858,6 @@ static struct ext4_ext_path *ext4_split_convert_exten= ts(handle_t *handle, split_flag |=3D ee_block + ee_len <=3D eof_block ? EXT4_EXT_MAY_ZEROOUT : 0; split_flag |=3D EXT4_EXT_MARK_UNWRIT2; - /* Convert to initialized */ - if (flags & EXT4_GET_BLOCKS_CONVERT) - split_flag |=3D EXT4_EXT_DATA_VALID2; } flags |=3D EXT4_GET_BLOCKS_SPLIT_NOMERGE; return ext4_split_extent(handle, inode, path, map, split_flag, flags, --=20 2.52.0 From nobody Sat Feb 7 21:53:15 2026 Received: from mx0b-001b2d01.pphosted.com (mx0b-001b2d01.pphosted.com [148.163.158.5]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D20E139C648; Fri, 23 Jan 2026 06:26:17 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=148.163.158.5 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769149591; cv=none; b=FncpW/q38tFkW/OXDwVqLITWjeQWr6KD5mkl4o4oCoFdLzApr2vPl/L8q7n1bAGj0awZAj9kUhbSXrHsd1kDpkbm0+NY0OghBROSIUK2hf2f8uFatUmc1gn/SMBQLu2f0CrghSEOAl3WyuM+VmmwttItjtCfbXWT67FBsmFxWoo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769149591; c=relaxed/simple; bh=49iU/pDPPN8/COZt8J+i2YNnaeo0Z4Ei0LNoJUcL9iY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=QJWYJqLXIrwuGvPtuVYV1Pv8HrpEZJbDIxzmXZMUOd8UKqK3KfCnLHcjbwiDEBPVo5rHoln8ec6VxyP+qNXX4iF1Bk/K2967ZLFXQZcm7rQs+pFhxy+DtoO8iZUlwI741WwLQCHr2DXljO3iLyHWCjMsl99kwgSOpCJFqTkHZ/A= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.ibm.com; spf=pass smtp.mailfrom=linux.ibm.com; dkim=pass (2048-bit key) header.d=ibm.com header.i=@ibm.com header.b=EXvAIOtg; arc=none smtp.client-ip=148.163.158.5 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.ibm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.ibm.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=ibm.com header.i=@ibm.com header.b="EXvAIOtg" Received: from pps.filterd (m0356516.ppops.net [127.0.0.1]) by mx0a-001b2d01.pphosted.com (8.18.1.2/8.18.1.2) with ESMTP id 60N1M0ku007709; Fri, 23 Jan 2026 06:26:02 GMT DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=ibm.com; h=cc :content-transfer-encoding:date:from:in-reply-to:message-id :mime-version:references:subject:to; s=pp1; bh=/CSwR1Won6R5+4Ws+ 177w8lPd87qTn/1kGQhFAYBblA=; b=EXvAIOtgUlgAbO0i8FsAX33dVzUZ5qeKF 9D+DeZTUUOwvirxgbSy/Pp7w0MEP7HysP7xMJRinUlae5fMujx4o3sIsgV0oOaZB mrFl5emsO3WHKua/K4Gkaz7jjrzySMymqZmWX9BgzEXD6u19cCQLP5tx22BtHT1t IayvusRXB2miP9CrzwJ/yhbKBfaDr0CgudeGaAqTM+Mcxf0OpJr9ZwCUrbHDVLML ara1AbWLPxS1po7xxOZGaJohpOIImjrW4zqO7WCZLE3Se6UkJT/V7Jv6hkhJFxcX 6c1cEpzuxKhAKz8eHBtWX1R96pQEheje+HmP+6lenTE8muHQbUM7Q== Received: from pps.reinject (localhost [127.0.0.1]) by mx0a-001b2d01.pphosted.com (PPS) with ESMTPS id 4bqyukmuw2-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Fri, 23 Jan 2026 06:26:01 +0000 (GMT) Received: from m0356516.ppops.net (m0356516.ppops.net [127.0.0.1]) by pps.reinject (8.18.1.12/8.18.0.8) with ESMTP id 60N6PFYI023420; Fri, 23 Jan 2026 06:26:01 GMT Received: from ppma13.dal12v.mail.ibm.com (dd.9e.1632.ip4.static.sl-reverse.com [50.22.158.221]) by mx0a-001b2d01.pphosted.com (PPS) with ESMTPS id 4bqyukmuvy-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Fri, 23 Jan 2026 06:26:01 +0000 (GMT) Received: from pps.filterd (ppma13.dal12v.mail.ibm.com [127.0.0.1]) by ppma13.dal12v.mail.ibm.com (8.18.1.2/8.18.1.2) with ESMTP id 60N4SaKL001168; Fri, 23 Jan 2026 06:26:00 GMT Received: from smtprelay06.fra02v.mail.ibm.com ([9.218.2.230]) by ppma13.dal12v.mail.ibm.com (PPS) with ESMTPS id 4brpyk7a4q-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Fri, 23 Jan 2026 06:26:00 +0000 Received: from smtpav02.fra02v.mail.ibm.com (smtpav02.fra02v.mail.ibm.com [10.20.54.101]) by smtprelay06.fra02v.mail.ibm.com (8.14.9/8.14.9/NCO v10.0) with ESMTP id 60N6Pwhs23200074 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK); Fri, 23 Jan 2026 06:25:58 GMT Received: from smtpav02.fra02v.mail.ibm.com (unknown [127.0.0.1]) by IMSVA (Postfix) with ESMTP id 8C40A2004F; Fri, 23 Jan 2026 06:25:58 +0000 (GMT) Received: from smtpav02.fra02v.mail.ibm.com (unknown [127.0.0.1]) by IMSVA (Postfix) with ESMTP id B170C20040; Fri, 23 Jan 2026 06:25:56 +0000 (GMT) Received: from li-dc0c254c-257c-11b2-a85c-98b6c1322444.ibm.com (unknown [9.39.26.206]) by smtpav02.fra02v.mail.ibm.com (Postfix) with ESMTP; Fri, 23 Jan 2026 06:25:56 +0000 (GMT) From: Ojaswin Mujoo To: linux-ext4@vger.kernel.org, "Theodore Ts'o" Cc: Ritesh Harjani , Zhang Yi , Jan Kara , libaokun1@huawei.com, linux-kernel@vger.kernel.org Subject: [PATCH v4 7/8] ext4: Refactor split and convert extents Date: Fri, 23 Jan 2026 11:55:38 +0530 Message-ID: <2084a383d69ceefbaa293b8fcf725365eca0a349.1769149131.git.ojaswin@linux.ibm.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-TM-AS-GCONF: 00 X-Proofpoint-Spam-Details-Enc: AW1haW4tMjYwMTIzMDA0NiBTYWx0ZWRfX09u63YW+d/3n 7LzDCG5Tu8WlKzfpwjr0C8pK2yMYJqHJHUjmGVqBVTjOV36co6jLMX/Z9DzVJzThVJXSXYpkrLO Ie4YvPcUZ7ODlYqYd/QPv0zCQzwJKfLf+WPHD8cCZYgj6B1E58hvAYRx5N6knLKIEWNsNusM9T5 8fiTA/eGdz01KY4bCTtuJ8ZzRnCVaEVkWvtfDJJMQPMxBZDbhcFnMQf5rXCzJkdKATa8RZ9hral RLUFsJKara0tX0TZwsa6L8afMX6yAtz5VLZB02+MNvgpieFJ4+FVj/FJ2lg3ErklK5nsb2PIHYK eSDPLwuTt9Sjrazk1kP43vAilxjDfe3v8IWltxQTmQfQP8Ddu+0ERb6vR0Q8rCud9qGsIqBZChg O+ttDIFDsnFl8MVmzSd5ddYqh2B4c1udDChbtgKbLOFTs0rNuWnVU/bnv9p+5iWhCcmnMohdKRl 1fJvzKBS+7s4xQaaJ0w== X-Authority-Analysis: v=2.4 cv=bsBBxUai c=1 sm=1 tr=0 ts=69731479 cx=c_pps a=AfN7/Ok6k8XGzOShvHwTGQ==:117 a=AfN7/Ok6k8XGzOShvHwTGQ==:17 a=vUbySO9Y5rIA:10 a=VkNPw1HP01LnGYTKEx00:22 a=i0EeH86SAAAA:8 a=VnNF1IyMAAAA:8 a=Roo43mxO8Zpiw3MSSV4A:9 X-Proofpoint-ORIG-GUID: SaKOvfM7Nq7_dmEuXp1FksnY2YMft2XZ X-Proofpoint-GUID: THjvxXPkZ9PwhBtCxLJItwEkak6Vu1Bp X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1121,Hydra:6.1.20,FMLib:17.12.100.49 definitions=2026-01-22_06,2026-01-22_02,2025-10-01_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 priorityscore=1501 suspectscore=0 phishscore=0 lowpriorityscore=0 bulkscore=0 impostorscore=0 malwarescore=0 clxscore=1015 adultscore=0 spamscore=0 classifier=typeunknown authscore=0 authtc= authcc= route=outbound adjust=0 reason=mlx scancount=1 engine=8.19.0-2601150000 definitions=main-2601230046 Content-Type: text/plain; charset="utf-8" ext4_split_convert_extents() has been historically prone to subtle bugs and inconsistent behavior due to the way all the various flags interact with the extent split and conversion process. For example, callers like ext4_convert_unwritten_extents_endio() and convert_initialized_extents() needed to open code extent conversion despite passing CONVERT or CONVERT_UNWRITTEN flags because ext4_split_convert_extents() wasn't performing the conversion. Hence, refactor ext4_split_convert_extents() to clearly enforce the semantics of each flag. The major changes here are: * Clearly separate the split and convert process: * ext4_split_extent() and ext4_split_extent_at() are now only responsible to perform the split. * ext4_split_convert_extents() is now responsible to perform extent conversion after calling ext4_split_extent() for splitting. * This helps get rid of all the MARK_UNWRIT* flags. * Clearly enforce the semantics of flags passed to ext4_split_convert_extents(): * EXT4_GET_BLOCKS_CONVERT: Will convert the split extent to written * EXT4_GET_BLOCKS_CONVERT_UNWRITTEN: Will convert the split extent to unwritten * Modify all callers to enforce the above semantics. * Use ext4_split_convert_extents() instead of ext4_split_extents() in ext4_ext_convert_to_initialized() for uniformity. * Now that ext4_split_convert_extents() is handling caching to es, we dont need to do it in ext4_split_extent_zeroout(). * Cleanup all callers open coding the conversion logic. Further, modify kuniy tests to pass flags based on the new semantics. >From an end user point of view, we should not see any changes in behavior of ext4. Reviewed-by: Jan Kara Reviewed-by: Zhang Yi Signed-off-by: Ojaswin Mujoo --- fs/ext4/extents.c | 277 +++++++++++++++++++--------------------------- 1 file changed, 112 insertions(+), 165 deletions(-) diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c index 8d709bfd299b..14f38b3cda27 100644 --- a/fs/ext4/extents.c +++ b/fs/ext4/extents.c @@ -41,8 +41,9 @@ */ #define EXT4_EXT_MAY_ZEROOUT 0x1 /* safe to zeroout if split fails \ due to ENOSPC */ -#define EXT4_EXT_MARK_UNWRIT1 0x2 /* mark first half unwritten */ -#define EXT4_EXT_MARK_UNWRIT2 0x4 /* mark second half unwritten */ +static struct ext4_ext_path *ext4_split_convert_extents( + handle_t *handle, struct inode *inode, struct ext4_map_blocks *map, + struct ext4_ext_path *path, int flags, unsigned int *allocated); =20 static __le32 ext4_extent_block_csum(struct inode *inode, struct ext4_extent_header *eh) @@ -84,8 +85,7 @@ static void ext4_extent_block_csum_set(struct inode *inod= e, 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); + ext4_lblk_t split, int flags); =20 static int ext4_ext_trunc_restart_fn(struct inode *inode, int *dropped) { @@ -333,15 +333,12 @@ ext4_force_split_extent_at(handle_t *handle, struct i= node *inode, struct ext4_ext_path *path, ext4_lblk_t lblk, int nofail) { - int unwritten =3D ext4_ext_is_unwritten(path[path->p_depth].p_ext); int flags =3D EXT4_EX_NOCACHE | EXT4_GET_BLOCKS_SPLIT_NOMERGE; =20 if (nofail) flags |=3D EXT4_GET_BLOCKS_METADATA_NOFAIL | EXT4_EX_NOFAIL; =20 - return ext4_split_extent_at(handle, inode, path, lblk, unwritten ? - EXT4_EXT_MARK_UNWRIT1|EXT4_EXT_MARK_UNWRIT2 : 0, - flags); + return ext4_split_extent_at(handle, inode, path, lblk, flags); } =20 static int @@ -3169,17 +3166,11 @@ static int ext4_ext_zeroout(struct inode *inode, st= ruct ext4_extent *ex) * @inode: the file inode * @path: the path to the extent * @split: the logical block where the extent is splitted. - * @split_flags: indicates if the extent could be zeroout if split fails, = and - * the states(init or unwritten) of new extents. * @flags: flags used to insert new extent to extent tree. * * * Splits extent [a, b] into two extents [a, @split) and [@split, b], stat= es - * of which are determined by split_flag. - * - * There are two cases: - * a> the extent are splitted into two extent. - * b> split is not needed, and just mark the extent. + * of which are same as the original extent. No conversion is performed. * * Return an extent path pointer on success, or an error pointer on failur= e. On * failure, the extent is restored to original state. @@ -3188,14 +3179,14 @@ static struct ext4_ext_path *ext4_split_extent_at(h= andle_t *handle, struct inode *inode, struct ext4_ext_path *path, ext4_lblk_t split, - int split_flag, int flags) + int flags) { ext4_fsblk_t newblock; ext4_lblk_t ee_block; struct ext4_extent *ex, newex, orig_ex; struct ext4_extent *ex2 =3D NULL; unsigned int ee_len, depth; - int err =3D 0, insert_err =3D 0; + int err =3D 0, insert_err =3D 0, is_unwrit =3D 0; =20 /* Do not cache extents that are in the process of being modified. */ flags |=3D EXT4_EX_NOCACHE; @@ -3209,39 +3200,24 @@ static struct ext4_ext_path *ext4_split_extent_at(h= andle_t *handle, ee_block =3D le32_to_cpu(ex->ee_block); ee_len =3D ext4_ext_get_actual_len(ex); newblock =3D split - ee_block + ext4_ext_pblock(ex); + is_unwrit =3D ext4_ext_is_unwritten(ex); =20 BUG_ON(split < ee_block || split >=3D (ee_block + ee_len)); - BUG_ON(!ext4_ext_is_unwritten(ex) && - split_flag & (EXT4_EXT_MAY_ZEROOUT | - EXT4_EXT_MARK_UNWRIT1 | - EXT4_EXT_MARK_UNWRIT2)); =20 - err =3D ext4_ext_get_access(handle, inode, path + depth); - if (err) + /* + * No split needed + */ + if (split =3D=3D ee_block) goto out; =20 - if (split =3D=3D ee_block) { - /* - * case b: block @split is the block that the extent begins with - * then we just change the state of the extent, and splitting - * is not needed. - */ - if (split_flag & EXT4_EXT_MARK_UNWRIT2) - ext4_ext_mark_unwritten(ex); - else - ext4_ext_mark_initialized(ex); - - if (!(flags & EXT4_GET_BLOCKS_SPLIT_NOMERGE)) - ext4_ext_try_to_merge(handle, inode, path, ex); - - err =3D ext4_ext_dirty(handle, inode, path + path->p_depth); + err =3D ext4_ext_get_access(handle, inode, path + depth); + if (err) goto out; - } =20 /* case a */ memcpy(&orig_ex, ex, sizeof(orig_ex)); ex->ee_len =3D cpu_to_le16(split - ee_block); - if (split_flag & EXT4_EXT_MARK_UNWRIT1) + if (is_unwrit) ext4_ext_mark_unwritten(ex); =20 /* @@ -3256,7 +3232,7 @@ static struct ext4_ext_path *ext4_split_extent_at(han= dle_t *handle, ex2->ee_block =3D cpu_to_le32(split); ex2->ee_len =3D cpu_to_le16(ee_len - (split - ee_block)); ext4_ext_store_pblock(ex2, newblock); - if (split_flag & EXT4_EXT_MARK_UNWRIT2) + if (is_unwrit) ext4_ext_mark_unwritten(ex2); =20 path =3D ext4_ext_insert_extent(handle, inode, path, &newex, flags); @@ -3389,20 +3365,10 @@ static int ext4_split_extent_zeroout(handle_t *hand= le, struct inode *inode, =20 ext4_ext_mark_initialized(ex); =20 - ext4_ext_dirty(handle, inode, path + path->p_depth); + ext4_ext_dirty(handle, inode, path + depth); if (err) return err; =20 - /* - * The whole extent is initialized and stable now so it can be added to - * es cache - */ - if (!(flags & EXT4_EX_NOCACHE)) - ext4_es_insert_extent(inode, le32_to_cpu(ex->ee_block), - ext4_ext_get_actual_len(ex), - ext4_ext_pblock(ex), - EXTENT_STATUS_WRITTEN, false); - return 0; } =20 @@ -3422,15 +3388,13 @@ static struct ext4_ext_path *ext4_split_extent(hand= le_t *handle, struct ext4_ext_path *path, struct ext4_map_blocks *map, int split_flag, int flags, - unsigned int *allocated) + unsigned int *allocated, bool *did_zeroout) { ext4_lblk_t ee_block, orig_ee_block; struct ext4_extent *ex; unsigned int ee_len, orig_ee_len, depth; int unwritten, orig_unwritten; - int split_flag1 =3D 0, flags1 =3D 0; int orig_err =3D 0; - int orig_flags =3D flags; =20 depth =3D ext_depth(inode); ex =3D path[depth].p_ext; @@ -3446,12 +3410,8 @@ static struct ext4_ext_path *ext4_split_extent(handl= e_t *handle, flags |=3D EXT4_EX_NOCACHE; =20 if (map->m_lblk + map->m_len < ee_block + ee_len) { - flags1 =3D flags | EXT4_GET_BLOCKS_SPLIT_NOMERGE; - if (unwritten) - split_flag1 |=3D EXT4_EXT_MARK_UNWRIT1 | - EXT4_EXT_MARK_UNWRIT2; path =3D ext4_split_extent_at(handle, inode, path, - map->m_lblk + map->m_len, split_flag1, flags1); + map->m_lblk + map->m_len, flags); if (IS_ERR(path)) goto try_zeroout; =20 @@ -3479,13 +3439,8 @@ static struct ext4_ext_path *ext4_split_extent(handl= e_t *handle, } =20 if (map->m_lblk >=3D ee_block) { - split_flag1 =3D 0; - if (unwritten) { - split_flag1 |=3D EXT4_EXT_MARK_UNWRIT1; - split_flag1 |=3D split_flag & EXT4_EXT_MARK_UNWRIT2; - } path =3D ext4_split_extent_at(handle, inode, path, map->m_lblk, - split_flag1, flags); + flags); if (IS_ERR(path)) goto try_zeroout; } @@ -3522,9 +3477,13 @@ static struct ext4_ext_path *ext4_split_extent(handl= e_t *handle, unwritten !=3D orig_unwritten)) goto out_free_path; =20 - if (ext4_split_extent_zeroout(handle, inode, path, map, orig_flags)) + if (ext4_split_extent_zeroout(handle, inode, path, map, flags)) goto out_free_path; =20 + /* zeroout succeeded */ + if (did_zeroout) + *did_zeroout =3D true; + success: if (allocated) { if (map->m_lblk + map->m_len > ee_block + ee_len) @@ -3575,7 +3534,6 @@ ext4_ext_convert_to_initialized(handle_t *handle, str= uct inode *inode, ext4_lblk_t ee_block, eof_block; unsigned int ee_len, depth, map_len =3D map->m_len; int err =3D 0; - int split_flag =3D 0; unsigned int max_zeroout =3D 0; =20 ext_debug(inode, "logical block %llu, max_blocks %u\n", @@ -3727,9 +3685,7 @@ ext4_ext_convert_to_initialized(handle_t *handle, str= uct inode *inode, * It is safe to convert extent to initialized via explicit * zeroout only if extent is fully inside i_size or new_size. */ - split_flag |=3D ee_block + ee_len <=3D eof_block ? EXT4_EXT_MAY_ZEROOUT := 0; - - if (EXT4_EXT_MAY_ZEROOUT & split_flag) + if (ee_block + ee_len <=3D eof_block) max_zeroout =3D sbi->s_extent_max_zeroout_kb >> (inode->i_sb->s_blocksize_bits - 10); =20 @@ -3784,8 +3740,8 @@ ext4_ext_convert_to_initialized(handle_t *handle, str= uct inode *inode, } =20 fallback: - path =3D ext4_split_extent(handle, inode, path, &split_map, split_flag, - flags | EXT4_GET_BLOCKS_CONVERT, NULL); + path =3D ext4_split_convert_extents(handle, inode, &split_map, path, + flags | EXT4_GET_BLOCKS_CONVERT, NULL); if (IS_ERR(path)) return path; out: @@ -3835,7 +3791,8 @@ static struct ext4_ext_path *ext4_split_convert_exten= ts(handle_t *handle, ext4_lblk_t ee_block; struct ext4_extent *ex; unsigned int ee_len; - int split_flag =3D 0, depth; + int split_flag =3D 0, depth, err =3D 0; + bool did_zeroout =3D false; =20 ext_debug(inode, "logical block %llu, max_blocks %u\n", (unsigned long long)map->m_lblk, map->m_len); @@ -3849,19 +3806,81 @@ static struct ext4_ext_path *ext4_split_convert_ext= ents(handle_t *handle, ee_block =3D le32_to_cpu(ex->ee_block); ee_len =3D ext4_ext_get_actual_len(ex); =20 - if (flags & (EXT4_GET_BLOCKS_UNWRIT_EXT | - EXT4_GET_BLOCKS_CONVERT)) { - /* - * It is safe to convert extent to initialized via explicit - * zeroout only if extent is fully inside i_size or new_size. - */ + /* No split needed */ + if (ee_block =3D=3D map->m_lblk && ee_len =3D=3D map->m_len) + goto convert; + + /* + * We don't use zeroout fallback for written to unwritten conversion as + * it is not as critical as endio and it might take unusually long. + * Also, it is only safe to convert extent to initialized via explicit + * zeroout only if extent is fully inside i_size or new_size. + */ + if (!(flags & EXT4_GET_BLOCKS_CONVERT_UNWRITTEN)) split_flag |=3D ee_block + ee_len <=3D eof_block ? - EXT4_EXT_MAY_ZEROOUT : 0; - split_flag |=3D EXT4_EXT_MARK_UNWRIT2; + EXT4_EXT_MAY_ZEROOUT : + 0; + + /* + * pass SPLIT_NOMERGE explicitly so we don't end up merging extents we + * just split. + */ + path =3D ext4_split_extent(handle, inode, path, map, split_flag, + flags | EXT4_GET_BLOCKS_SPLIT_NOMERGE, + allocated, &did_zeroout); + if (IS_ERR(path)) + return path; + +convert: + path =3D ext4_find_extent(inode, map->m_lblk, path, flags); + if (IS_ERR(path)) + return path; + + depth =3D ext_depth(inode); + ex =3D path[depth].p_ext; + + /* + * Conversion is already handled in case of zeroout + */ + if (!did_zeroout) { + err =3D ext4_ext_get_access(handle, inode, path + depth); + if (err) + goto err; + + if (flags & EXT4_GET_BLOCKS_CONVERT) + ext4_ext_mark_initialized(ex); + else if (flags & EXT4_GET_BLOCKS_CONVERT_UNWRITTEN) + ext4_ext_mark_unwritten(ex); + + if (!(flags & EXT4_GET_BLOCKS_SPLIT_NOMERGE)) + /* + * note: ext4_ext_correct_indexes() isn't needed here because + * borders are not changed + */ + ext4_ext_try_to_merge(handle, inode, path, ex); + + err =3D ext4_ext_dirty(handle, inode, path + depth); + if (err) + goto err; + } + + /* Lets update the extent status tree after conversion */ + if (!(flags & EXT4_EX_NOCACHE)) + ext4_es_insert_extent(inode, le32_to_cpu(ex->ee_block), + ext4_ext_get_actual_len(ex), + ext4_ext_pblock(ex), + ext4_ext_is_unwritten(ex) ? + EXTENT_STATUS_UNWRITTEN : + EXTENT_STATUS_WRITTEN, + false); + +err: + if (err) { + ext4_free_ext_path(path); + return ERR_PTR(err); } - flags |=3D EXT4_GET_BLOCKS_SPLIT_NOMERGE; - return ext4_split_extent(handle, inode, path, map, split_flag, flags, - allocated); + + return path; } =20 static struct ext4_ext_path * @@ -3873,7 +3892,6 @@ ext4_convert_unwritten_extents_endio(handle_t *handle= , struct inode *inode, ext4_lblk_t ee_block; unsigned int ee_len; int depth; - int err =3D 0; =20 depth =3D ext_depth(inode); ex =3D path[depth].p_ext; @@ -3883,41 +3901,8 @@ ext4_convert_unwritten_extents_endio(handle_t *handl= e, struct inode *inode, ext_debug(inode, "logical block %llu, max_blocks %u\n", (unsigned long long)ee_block, ee_len); =20 - if (ee_block !=3D map->m_lblk || ee_len > map->m_len) { - path =3D ext4_split_convert_extents(handle, inode, map, path, - flags, NULL); - if (IS_ERR(path)) - return path; - - path =3D ext4_find_extent(inode, map->m_lblk, path, flags); - if (IS_ERR(path)) - return path; - depth =3D ext_depth(inode); - ex =3D path[depth].p_ext; - } - - err =3D ext4_ext_get_access(handle, inode, path + depth); - if (err) - goto errout; - /* first mark the extent as initialized */ - ext4_ext_mark_initialized(ex); - - /* note: ext4_ext_correct_indexes() isn't needed here because - * borders are not changed - */ - ext4_ext_try_to_merge(handle, inode, path, ex); - - /* Mark modified extent as dirty */ - err =3D ext4_ext_dirty(handle, inode, path + path->p_depth); - if (err) - goto errout; - - ext4_ext_show_leaf(inode, path); - return path; - -errout: - ext4_free_ext_path(path); - return ERR_PTR(err); + return ext4_split_convert_extents(handle, inode, map, path, flags, + NULL); } =20 static struct ext4_ext_path * @@ -3931,7 +3916,6 @@ convert_initialized_extent(handle_t *handle, struct i= node *inode, ext4_lblk_t ee_block; unsigned int ee_len; int depth; - int err =3D 0; =20 /* * Make sure that the extent is no bigger than we support with @@ -3948,40 +3932,11 @@ convert_initialized_extent(handle_t *handle, struct= inode *inode, ext_debug(inode, "logical block %llu, max_blocks %u\n", (unsigned long long)ee_block, ee_len); =20 - if (ee_block !=3D map->m_lblk || ee_len > map->m_len) { - path =3D ext4_split_convert_extents(handle, inode, map, path, - flags, NULL); - if (IS_ERR(path)) - return path; - - path =3D ext4_find_extent(inode, map->m_lblk, path, flags); - if (IS_ERR(path)) - return path; - depth =3D ext_depth(inode); - ex =3D path[depth].p_ext; - if (!ex) { - EXT4_ERROR_INODE(inode, "unexpected hole at %lu", - (unsigned long) map->m_lblk); - err =3D -EFSCORRUPTED; - goto errout; - } - } - - err =3D ext4_ext_get_access(handle, inode, path + depth); - if (err) - goto errout; - /* first mark the extent as unwritten */ - ext4_ext_mark_unwritten(ex); - - /* note: ext4_ext_correct_indexes() isn't needed here because - * borders are not changed - */ - ext4_ext_try_to_merge(handle, inode, path, ex); + path =3D ext4_split_convert_extents(handle, inode, map, path, flags, + NULL); + if (IS_ERR(path)) + return path; =20 - /* Mark modified extent as dirty */ - err =3D ext4_ext_dirty(handle, inode, path + path->p_depth); - if (err) - goto errout; ext4_ext_show_leaf(inode, path); =20 ext4_update_inode_fsync_trans(handle, inode, 1); @@ -3991,10 +3946,6 @@ convert_initialized_extent(handle_t *handle, struct = inode *inode, *allocated =3D map->m_len; map->m_len =3D *allocated; return path; - -errout: - ext4_free_ext_path(path); - return ERR_PTR(err); } =20 static struct ext4_ext_path * @@ -5629,7 +5580,7 @@ static int ext4_insert_range(struct file *file, loff_= t offset, loff_t len) struct ext4_extent *extent; ext4_lblk_t start_lblk, len_lblk, ee_start_lblk =3D 0; unsigned int credits, ee_len; - int ret, depth, split_flag =3D 0; + int ret, depth; loff_t start; =20 trace_ext4_insert_range(inode, offset, len); @@ -5700,12 +5651,8 @@ static int ext4_insert_range(struct file *file, loff= _t offset, loff_t len) */ if ((start_lblk > ee_start_lblk) && (start_lblk < (ee_start_lblk + ee_len))) { - if (ext4_ext_is_unwritten(extent)) - split_flag =3D EXT4_EXT_MARK_UNWRIT1 | - EXT4_EXT_MARK_UNWRIT2; path =3D ext4_split_extent_at(handle, inode, path, - start_lblk, split_flag, - EXT4_EX_NOCACHE | + start_lblk, EXT4_EX_NOCACHE | EXT4_GET_BLOCKS_SPLIT_NOMERGE | EXT4_GET_BLOCKS_METADATA_NOFAIL); } --=20 2.52.0 From nobody Sat Feb 7 21:53:15 2026 Received: from mx0b-001b2d01.pphosted.com (mx0b-001b2d01.pphosted.com [148.163.158.5]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 69B1538946E; Fri, 23 Jan 2026 06:26:17 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=148.163.158.5 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769149586; cv=none; b=Jwidxlm037Ue/1r5zvGa1V71sws+B6oe80/dxqYlGx63D4fZY1Y98BgqKmmgxc4E47uCvCvziQtjadlKmjiyRxrRJvVWUEQv+bNl07FcQwuEdwVbjUK9uU9+MrVw0U+pzbC850ndpElkcuhug/BexVp21bv4sjxc9Vqm18DYmhU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769149586; c=relaxed/simple; bh=SDPrmZkaloj1OviWw6+W6I5u87D/L8PHs4zs09hNsXo=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=W9BGSuVPTrZTTwcL5+hdusWIzrbbHNHOGQJxzgMQOg5+jP1VtRPfthx3CReT6eE2kwo2cwLxWbCUo9ayE5n/SfHtgS2hXHP13IB7CGjJXHlPoF/jCtMc1dkTt4vvzm5BAoSqgjp6iu5Ofvp7OMfq3QRR3oSY+RUMuLozOhAiZic= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.ibm.com; spf=pass smtp.mailfrom=linux.ibm.com; dkim=pass (2048-bit key) header.d=ibm.com header.i=@ibm.com header.b=LEiDmTs0; arc=none smtp.client-ip=148.163.158.5 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.ibm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.ibm.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=ibm.com header.i=@ibm.com header.b="LEiDmTs0" Received: from pps.filterd (m0360072.ppops.net [127.0.0.1]) by mx0a-001b2d01.pphosted.com (8.18.1.2/8.18.1.2) with ESMTP id 60MLlSvG021326; Fri, 23 Jan 2026 06:26:04 GMT DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=ibm.com; h=cc :content-transfer-encoding:date:from:in-reply-to:message-id :mime-version:references:subject:to; s=pp1; bh=4ADFMMHAXiKaIiNR6 vBP9uCUmbxSmzucAMe3Fj04WOo=; b=LEiDmTs0Zb7M/Z4ViYIiatE01LhV/aHs3 LvpXFSK6rQcp2fR0C5SBodDNz5ofZADdgbNRO5kRt5pxEpS0H4LhzOqW4q1tkIsX 50dS/TdYrjJdpjI7q9h3gcIy4mrnFUyWOTrbkJHP1cCuTSCuumQz4Xfpb5/f0Kyc Ct6cwBjQPf8HcvkHULTQWrxL1b7FGYV8e2wy0Qe3LXlGWrYZ8qb5w6+56fyZPuZZ 5xNt0NONK+0W4niGS4DEJfslDsRWeE4HfhS6TY8Ie8zmm+latOl2IkxPSDu7fu9D Sjc4EUfZIPB+8qw/DFlJjep2+hLcgjnjfldoPJwFCb9Fw1jl2qr9w== Received: from pps.reinject (localhost [127.0.0.1]) by mx0a-001b2d01.pphosted.com (PPS) with ESMTPS id 4bt612h1ct-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Fri, 23 Jan 2026 06:26:04 +0000 (GMT) Received: from m0360072.ppops.net (m0360072.ppops.net [127.0.0.1]) by pps.reinject (8.18.1.12/8.18.0.8) with ESMTP id 60N6PbbR005279; Fri, 23 Jan 2026 06:26:03 GMT Received: from ppma11.dal12v.mail.ibm.com (db.9e.1632.ip4.static.sl-reverse.com [50.22.158.219]) by mx0a-001b2d01.pphosted.com (PPS) with ESMTPS id 4bt612h1cp-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Fri, 23 Jan 2026 06:26:03 +0000 (GMT) Received: from pps.filterd (ppma11.dal12v.mail.ibm.com [127.0.0.1]) by ppma11.dal12v.mail.ibm.com (8.18.1.2/8.18.1.2) with ESMTP id 60N4n0L0006451; Fri, 23 Jan 2026 06:26:02 GMT Received: from smtprelay04.fra02v.mail.ibm.com ([9.218.2.228]) by ppma11.dal12v.mail.ibm.com (PPS) with ESMTPS id 4brqf1y6hf-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Fri, 23 Jan 2026 06:26:02 +0000 Received: from smtpav02.fra02v.mail.ibm.com (smtpav02.fra02v.mail.ibm.com [10.20.54.101]) by smtprelay04.fra02v.mail.ibm.com (8.14.9/8.14.9/NCO v10.0) with ESMTP id 60N6Q0Vh16581038 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK); Fri, 23 Jan 2026 06:26:00 GMT Received: from smtpav02.fra02v.mail.ibm.com (unknown [127.0.0.1]) by IMSVA (Postfix) with ESMTP id BA27B2004B; Fri, 23 Jan 2026 06:26:00 +0000 (GMT) Received: from smtpav02.fra02v.mail.ibm.com (unknown [127.0.0.1]) by IMSVA (Postfix) with ESMTP id DAF5F20040; Fri, 23 Jan 2026 06:25:58 +0000 (GMT) Received: from li-dc0c254c-257c-11b2-a85c-98b6c1322444.ibm.com (unknown [9.39.26.206]) by smtpav02.fra02v.mail.ibm.com (Postfix) with ESMTP; Fri, 23 Jan 2026 06:25:58 +0000 (GMT) From: Ojaswin Mujoo To: linux-ext4@vger.kernel.org, "Theodore Ts'o" Cc: Ritesh Harjani , Zhang Yi , Jan Kara , libaokun1@huawei.com, linux-kernel@vger.kernel.org Subject: [PATCH v4 8/8] ext4: Allow zeroout when doing written to unwritten split Date: Fri, 23 Jan 2026 11:55:39 +0530 Message-ID: <1c3349020b8e098a63f293b84bc8a9b56011cef4.1769149131.git.ojaswin@linux.ibm.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-TM-AS-GCONF: 00 X-Proofpoint-GUID: I9z7CTj-u72IjC0T-o7si2BHoXvdmbP_ X-Authority-Analysis: v=2.4 cv=LaIxKzfi c=1 sm=1 tr=0 ts=6973147c cx=c_pps a=aDMHemPKRhS1OARIsFnwRA==:117 a=aDMHemPKRhS1OARIsFnwRA==:17 a=vUbySO9Y5rIA:10 a=VkNPw1HP01LnGYTKEx00:22 a=i0EeH86SAAAA:8 a=VnNF1IyMAAAA:8 a=kZK9M-q0HhXr2Cl_658A:9 X-Proofpoint-ORIG-GUID: 4AdbEr1IgerHfGUAiJDAGuAW75nYRANQ X-Proofpoint-Spam-Details-Enc: AW1haW4tMjYwMTIzMDA0NiBTYWx0ZWRfX6RGiokzR+3Kr +i4AfjRbii8x5MZOK/Bl64jiFjURJItbYwb7o4HsZPkejOaiP0yp3c6Rm6xoDhvrfEsIxfUO3vK /1XI07+sX4hLed/PBKT1OUlBXlVSGAmhWuiJLOyNngUK8AMj+RfO/SEWovvI0O3DbDMMWfa4yPE 28CfPjk0LibBP5AzldmthCPMhZsxgiZolkqsD7HVNS7PwClJ99b6xJuhkM3qKAxj95JLqnd1MSK wgg/wwhYBfjSKcvScZa4ZOspJb7hV2aQ/lKVTupPdgpSrIhWVftSumMz6ajChFsDYXXeo5gF2tx zmcqM+ucKV261SX+uFlfFzZFJxzUWdEAdERKTyvpRpFmAOqlnnqbJyDOcllIgMkR2jaVVbjTPdj E67KNeJSwNcq0mnu0Hrtcmj2pewdlzxT+DEmkXavGsInJvwMHYwjOrB2WKDP0LUawlH3K7sskOY X0UdOWynuUxVku8KnKg== X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1121,Hydra:6.1.20,FMLib:17.12.100.49 definitions=2026-01-22_06,2026-01-22_02,2025-10-01_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 bulkscore=0 adultscore=0 phishscore=0 priorityscore=1501 lowpriorityscore=0 suspectscore=0 clxscore=1015 impostorscore=0 spamscore=0 malwarescore=0 classifier=typeunknown authscore=0 authtc= authcc= route=outbound adjust=0 reason=mlx scancount=1 engine=8.19.0-2601150000 definitions=main-2601230046 Content-Type: text/plain; charset="utf-8" Currently, when we are doing an extent split and convert operation of written to unwritten extent (example, as done by ZERO_RANGE), we don't allow the zeroout fallback in case the extent tree manipulation fails. This is mostly because zeroout might take unsually long and the fact that this code path is more tolerant to failures than endio. Since we have zeroout machinery in place, we might as well use it hence lift this restriction. To mitigate zeroout taking too long respect the max zeroout limit here so that the operation finishes relatively fast. Also, add kunit tests for this case. Reviewed-by: Jan Kara Reviewed-by: Zhang Yi Signed-off-by: Ojaswin Mujoo --- fs/ext4/extents-test.c | 98 +++++++++++++++++++++++++++++++++++++++++- fs/ext4/extents.c | 33 ++++++++++---- 2 files changed, 122 insertions(+), 9 deletions(-) diff --git a/fs/ext4/extents-test.c b/fs/ext4/extents-test.c index 3176bf7686b5..4879e68e465d 100644 --- a/fs/ext4/extents-test.c +++ b/fs/ext4/extents-test.c @@ -613,11 +613,57 @@ static const struct kunit_ext_test_param test_split_c= onvert_params[] =3D { .is_unwrit =3D 0 } }, .is_zeroout_test =3D 1, .nr_exp_data_segs =3D 3, + .exp_data_state =3D { { .exp_char =3D 0, .off_blk =3D 0, .len_blk =3D 1= }, + { .exp_char =3D 'X', .off_blk =3D 1, .len_blk =3D EXT_DATA_LEN - = 2 }, + { .exp_char =3D 0, .off_blk =3D EXT_DATA_LEN - 1, .len_blk =3D 1 = } } }, + + /* writ to unwrit splits */ + { .desc =3D "split writ extent to 2 extents and convert 1st half unwrit (= zeroout)", + .type =3D TEST_SPLIT_CONVERT, + .is_unwrit_at_start =3D 0, + .split_flags =3D EXT4_GET_BLOCKS_CONVERT_UNWRITTEN, + .split_map =3D { .m_lblk =3D EXT_DATA_LBLK, .m_len =3D 1 }, + .nr_exp_ext =3D 1, + .exp_ext_state =3D { { .ex_lblk =3D EXT_DATA_LBLK, + .ex_len =3D EXT_DATA_LEN, + .is_unwrit =3D 0 } }, + .is_zeroout_test =3D 1, + .nr_exp_data_segs =3D 2, .exp_data_state =3D { { .exp_char =3D 0, .off_blk =3D 0, .len_blk =3D 1= }, { .exp_char =3D 'X', .off_blk =3D 1, - .len_blk =3D EXT_DATA_LEN - 2 }, + .len_blk =3D EXT_DATA_LEN - 1 } } }, + { .desc =3D "split writ extent to 2 extents and convert 2nd half unwrit (= zeroout)", + .type =3D TEST_SPLIT_CONVERT, + .is_unwrit_at_start =3D 0, + .split_flags =3D EXT4_GET_BLOCKS_CONVERT_UNWRITTEN, + .split_map =3D { .m_lblk =3D EXT_DATA_LBLK + 1, .m_len =3D EXT_DATA_LEN= - 1 }, + .nr_exp_ext =3D 1, + .exp_ext_state =3D { { .ex_lblk =3D EXT_DATA_LBLK, + .ex_len =3D EXT_DATA_LEN, + .is_unwrit =3D 0 } }, + .is_zeroout_test =3D 1, + .nr_exp_data_segs =3D 2, + .exp_data_state =3D { { .exp_char =3D 'X', .off_blk =3D 0, .len_blk =3D= 1 }, + { .exp_char =3D 0, + .off_blk =3D 1, + .len_blk =3D EXT_DATA_LEN - 1 } } }, + { .desc =3D "split writ extent to 3 extents and convert 2nd half unwrit (= zeroout)", + .type =3D TEST_SPLIT_CONVERT, + .is_unwrit_at_start =3D 0, + .split_flags =3D EXT4_GET_BLOCKS_CONVERT_UNWRITTEN, + .split_map =3D { .m_lblk =3D EXT_DATA_LBLK + 1, .m_len =3D EXT_DATA_LEN= - 2 }, + .nr_exp_ext =3D 1, + .exp_ext_state =3D { { .ex_lblk =3D EXT_DATA_LBLK, + .ex_len =3D EXT_DATA_LEN, + .is_unwrit =3D 0 } }, + .is_zeroout_test =3D 1, + .nr_exp_data_segs =3D 3, + .exp_data_state =3D { { .exp_char =3D 'X', .off_blk =3D 0, .len_blk =3D= 1 }, { .exp_char =3D 0, + .off_blk =3D 1, + .len_blk =3D EXT_DATA_LEN - 2 }, + { .exp_char =3D 'X', .off_blk =3D EXT_DATA_LEN - 1, .len_blk =3D 1 } } }, }; @@ -667,6 +713,56 @@ static const struct kunit_ext_test_param test_convert_= initialized_params[] =3D { .ex_len =3D 1, .is_unwrit =3D 0 } }, .is_zeroout_test =3D 0 }, + + /* writ to unwrit splits (zeroout) */ + { .desc =3D "split writ extent to 2 extents and convert 1st half unwrit (= zeroout)", + .type =3D TEST_CREATE_BLOCKS, + .is_unwrit_at_start =3D 0, + .split_flags =3D EXT4_GET_BLOCKS_CONVERT_UNWRITTEN, + .split_map =3D { .m_lblk =3D EXT_DATA_LBLK, .m_len =3D 1 }, + .nr_exp_ext =3D 1, + .exp_ext_state =3D { { .ex_lblk =3D EXT_DATA_LBLK, + .ex_len =3D EXT_DATA_LEN, + .is_unwrit =3D 0 } }, + .is_zeroout_test =3D 1, + .nr_exp_data_segs =3D 2, + .exp_data_state =3D { { .exp_char =3D 0, .off_blk =3D 0, .len_blk =3D 1= }, + { .exp_char =3D 'X', + .off_blk =3D 1, + .len_blk =3D EXT_DATA_LEN - 1 } } }, + { .desc =3D "split writ extent to 2 extents and convert 2nd half unwrit (= zeroout)", + .type =3D TEST_CREATE_BLOCKS, + .is_unwrit_at_start =3D 0, + .split_flags =3D EXT4_GET_BLOCKS_CONVERT_UNWRITTEN, + .split_map =3D { .m_lblk =3D EXT_DATA_LBLK + 1, .m_len =3D EXT_DATA_LEN= - 1 }, + .nr_exp_ext =3D 1, + .exp_ext_state =3D { { .ex_lblk =3D EXT_DATA_LBLK, + .ex_len =3D EXT_DATA_LEN, + .is_unwrit =3D 0 } }, + .is_zeroout_test =3D 1, + .nr_exp_data_segs =3D 2, + .exp_data_state =3D { { .exp_char =3D 'X', .off_blk =3D 0, .len_blk =3D= 1 }, + { .exp_char =3D 0, + .off_blk =3D 1, + .len_blk =3D EXT_DATA_LEN - 1 } } }, + { .desc =3D "split writ extent to 3 extents and convert 2nd half unwrit (= zeroout)", + .type =3D TEST_CREATE_BLOCKS, + .is_unwrit_at_start =3D 0, + .split_flags =3D EXT4_GET_BLOCKS_CONVERT_UNWRITTEN, + .split_map =3D { .m_lblk =3D EXT_DATA_LBLK + 1, .m_len =3D EXT_DATA_LEN= - 2 }, + .nr_exp_ext =3D 1, + .exp_ext_state =3D { { .ex_lblk =3D EXT_DATA_LBLK, + .ex_len =3D EXT_DATA_LEN, + .is_unwrit =3D 0 } }, + .is_zeroout_test =3D 1, + .nr_exp_data_segs =3D 3, + .exp_data_state =3D { { .exp_char =3D 'X', .off_blk =3D 0, .len_blk =3D= 1 }, + { .exp_char =3D 0, + .off_blk =3D 1, + .len_blk =3D EXT_DATA_LEN - 2 }, + { .exp_char =3D 'X', + .off_blk =3D EXT_DATA_LEN - 1, + .len_blk =3D 1 } } }, }; =20 /* Tests to trigger ext4_ext_map_blocks() -> ext4_ext_handle_unwritten_exn= tents() */ diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c index 14f38b3cda27..3630b27e4fd7 100644 --- a/fs/ext4/extents.c +++ b/fs/ext4/extents.c @@ -3462,6 +3462,15 @@ static struct ext4_ext_path *ext4_split_extent(handl= e_t *handle, if (!(split_flag & EXT4_EXT_MAY_ZEROOUT)) goto out_orig_err; =20 + if (flags & EXT4_GET_BLOCKS_CONVERT_UNWRITTEN) { + int max_zeroout_blks =3D + EXT4_SB(inode->i_sb)->s_extent_max_zeroout_kb >> + (inode->i_sb->s_blocksize_bits - 10); + + if (map->m_len > max_zeroout_blks) + goto out_orig_err; + } + path =3D ext4_find_extent(inode, map->m_lblk, NULL, flags); if (IS_ERR(path)) goto out_orig_err; @@ -3811,15 +3820,10 @@ static struct ext4_ext_path *ext4_split_convert_ext= ents(handle_t *handle, goto convert; =20 /* - * We don't use zeroout fallback for written to unwritten conversion as - * it is not as critical as endio and it might take unusually long. - * Also, it is only safe to convert extent to initialized via explicit + * It is only safe to convert extent to initialized via explicit * zeroout only if extent is fully inside i_size or new_size. */ - if (!(flags & EXT4_GET_BLOCKS_CONVERT_UNWRITTEN)) - split_flag |=3D ee_block + ee_len <=3D eof_block ? - EXT4_EXT_MAY_ZEROOUT : - 0; + split_flag |=3D ee_block + ee_len <=3D eof_block ? EXT4_EXT_MAY_ZEROOUT := 0; =20 /* * pass SPLIT_NOMERGE explicitly so we don't end up merging extents we @@ -3941,7 +3945,20 @@ convert_initialized_extent(handle_t *handle, struct = inode *inode, =20 ext4_update_inode_fsync_trans(handle, inode, 1); =20 - map->m_flags |=3D EXT4_MAP_UNWRITTEN; + /* + * The extent might be initialized in case of zeroout. + */ + path =3D ext4_find_extent(inode, map->m_lblk, path, flags); + if (IS_ERR(path)) + return path; + + depth =3D ext_depth(inode); + ex =3D path[depth].p_ext; + + if (ext4_ext_is_unwritten(ex)) + map->m_flags |=3D EXT4_MAP_UNWRITTEN; + else + map->m_flags |=3D EXT4_MAP_MAPPED; if (*allocated > map->m_len) *allocated =3D map->m_len; map->m_len =3D *allocated; --=20 2.52.0