From: "Theodore Ts'o" <tytso@mit.edu>
To: Ext4 Developers List <linux-ext4@vger.kernel.org>
Cc: "Theodore Ts'o" <tytso@mit.edu>
Subject: [PATCH 02/27] ext4: add KUnit tests for fs/ext4/extents.c
Date: Wed, 11 Nov 2026 23:06:13 -0500 [thread overview]
Message-ID: <20261112040638.1576836-3-tytso@mit.edu> (raw)
In-Reply-To: <20261112040638.1576836-1-tytso@mit.edu>
Expand the ext4_extents_test KUnit test suite in fs/ext4/extents-test.c
to cover the core extent tree operations in fs/ext4/extents.c:
- 48-bit physical block encoding/decoding (ext4_ext_pblock,
ext4_ext_store_pblock, ext4_idx_pblock, ext4_idx_store_pblock) and
unwritten extent length encoding (ext4_ext_mark_unwritten,
ext4_ext_is_unwritten, ext4_ext_get_actual_len,
ext4_ext_mark_initialized).
- Extent tree initialization (ext4_ext_tree_init) and header/entry
validation (ext4_ext_check_inode, __ext4_ext_check) across 13
parameterized corruption cases (magic, depth, max, entries, empty
non-root index, zero-length extent, logical block wrap-around,
out-of-bounds physical blocks, overlapping/unsorted extents and
indexes, and parent/leaf logical block mismatch).
- Metadata checksum calculation and verification on external extent
blocks when EXT4_FEATURE_RO_COMPAT_METADATA_CSUM is enabled.
- Root and multi-level (depth >= 1) extent tree lookup and navigation
(ext4_find_extent, ext4_ext_search_left, ext4_ext_search_right,
ext4_ext_next_allocated_block, ext4_ext_next_leaf_block) and extent
status precaching (ext4_ext_precache) using in-memory simulated
metadata buffer_heads via an ext4_ext_getblk() KUnit static stub.
- Extent merge rules (ext4_can_extents_be_merged) and insertion
(ext4_ext_insert_extent), including forward/backward/three-way
merging, nomerge flag handling, tree depth growth
(ext4_ext_grow_indepth), node splitting (ext4_ext_split), and tree
collapse back into the root inode (ext4_ext_try_to_merge_up).
- Extent space removal and truncation (ext4_ext_remove_space,
ext4_ext_truncate) across tail truncates, partial trims, middle hole
punches, multi-level index removal (ext4_ext_rm_idx), and bigalloc
partial-cluster nofree and pending cluster rereservation
(ext4_rereserve_cluster).
- Block mapping and allocation (ext4_ext_map_blocks), including hole
lookup, delayed-extent hole adjustment, QUERY_LAST_IN_LEAF, block
allocation, overlap clamping, ENOSPC handling, unwritten-to-written
conversion with neighbor merging, and bigalloc implied cluster
allocation (ext4_map_cluster_from_extent).
- Extent shifting (ext4_ext_shift_extents) for FALLOC_FL_COLLAPSE_RANGE
and FALLOC_FL_INSERT_RANGE, extent swapping (ext4_swap_extents),
cluster mapping queries (ext4_clu_mapped), JBD2 transaction credit
calculations (ext4_ext_calc_credits_for_single_extent,
ext4_ext_index_trans_blocks), and fast-commit replay helpers
(ext4_ext_replay_update_ex, ext4_ext_replay_shrink_inode,
ext4_ext_replay_set_iblocks).
- Fix ext4_ext_path memory leaks in the existing test_split_convert()
helpers and isolate the mock extent status shrinker from background
reclaim.
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
---
fs/ext4/ext4_extents.h | 21 +
fs/ext4/ext4_jbd2.c | 8 +
fs/ext4/extents-test.c | 1886 ++++++++++++++++++++++++++++++++++++++--
fs/ext4/extents.c | 83 +-
fs/ext4/inode.c | 5 +
fs/ext4/mballoc.c | 7 +
6 files changed, 1938 insertions(+), 72 deletions(-)
diff --git a/fs/ext4/ext4_extents.h b/fs/ext4/ext4_extents.h
index ebaf7cc42430..40e6b3a56f71 100644
--- a/fs/ext4/ext4_extents.h
+++ b/fs/ext4/ext4_extents.h
@@ -268,6 +268,8 @@ extern int __ext4_ext_dirty(const char *where, unsigned int line,
handle_t *handle, struct inode *inode,
struct ext4_ext_path *path);
extern int ext4_ext_zeroout(struct inode *inode, struct ext4_extent *ex);
+extern struct buffer_head *ext4_ext_getblk(struct inode *inode,
+ ext4_fsblk_t pblk, gfp_t gfp_flags);
#if IS_ENABLED(CONFIG_EXT4_KUNIT_TESTS)
extern int ext4_ext_space_root_idx_test(struct inode *inode, int check);
extern struct ext4_ext_path *ext4_split_convert_extents_test(
@@ -275,6 +277,25 @@ extern struct ext4_ext_path *ext4_split_convert_extents_test(
struct ext4_map_blocks *map,
struct ext4_ext_path *path,
int flags, unsigned int *allocated);
+extern int ext4_ext_check_test(struct inode *inode,
+ struct ext4_extent_header *eh, int depth,
+ ext4_fsblk_t pblk, ext4_lblk_t lblk);
+extern void ext4_extent_block_csum_set_test(struct inode *inode,
+ struct ext4_extent_header *eh);
+extern int ext4_can_extents_be_merged_test(struct inode *inode,
+ struct ext4_extent *ex1,
+ struct ext4_extent *ex2);
+extern int ext4_ext_search_left_test(struct inode *inode,
+ struct ext4_ext_path *path,
+ ext4_lblk_t *logical, ext4_fsblk_t *phys);
+extern int ext4_ext_search_right_test(struct inode *inode,
+ struct ext4_ext_path *path,
+ ext4_lblk_t *logical, ext4_fsblk_t *phys,
+ struct ext4_extent *ret_ex, int flags);
+extern ext4_lblk_t ext4_ext_next_leaf_block_test(struct ext4_ext_path *path);
+extern int ext4_ext_shift_extents_test(struct inode *inode, handle_t *handle,
+ ext4_lblk_t start, ext4_lblk_t shift,
+ enum SHIFT_DIRECTION direction);
#endif
#endif /* _EXT4_EXTENTS */
diff --git a/fs/ext4/ext4_jbd2.c b/fs/ext4/ext4_jbd2.c
index 53ddedb52a6f..d6ffb6f08606 100644
--- a/fs/ext4/ext4_jbd2.c
+++ b/fs/ext4/ext4_jbd2.c
@@ -4,6 +4,7 @@
*/
#include "ext4_jbd2.h"
+#include <kunit/static_stub.h>
#include <trace/events/ext4.h>
@@ -379,6 +380,9 @@ int __ext4_handle_dirty_metadata(const char *where, unsigned int line,
{
int err = 0;
+ KUNIT_STATIC_STUB_REDIRECT(__ext4_handle_dirty_metadata, where, line,
+ handle, inode, bh);
+
might_sleep();
set_buffer_meta(bh);
@@ -431,3 +435,7 @@ int __ext4_handle_dirty_metadata(const char *where, unsigned int line,
}
return err;
}
+
+#if IS_ENABLED(CONFIG_EXT4_KUNIT_TESTS)
+EXPORT_SYMBOL_FOR_EXT4_TEST(__ext4_handle_dirty_metadata);
+#endif
diff --git a/fs/ext4/extents-test.c b/fs/ext4/extents-test.c
index c3836ecb89f9..705648186218 100644
--- a/fs/ext4/extents-test.c
+++ b/fs/ext4/extents-test.c
@@ -3,60 +3,75 @@
* Written by Ojaswin Mujoo <ojaswin@linux.ibm.com> (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 converting 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_extents(),
- * 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 zeroes
- * 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 with
- * extent tree, call ext4_split_convert_extents() and check the final state 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 functions
- * 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 confirm
- * everything went well.
+ * fs/ext4/extents.c, including extent tree initialization and validation,
+ * binary search and multi-level navigation, extent insertion, merging,
+ * splitting, tree depth growth and collapse, space removal and bigalloc
+ * partial cluster handling, block mapping, unwritten-to-written conversion,
+ * zeroout fallback, extent shifting, extent swapping, cluster mapping,
+ * transaction credit calculation, and fast-commit replay helpers.
*/
#include <kunit/test.h>
#include <kunit/static_stub.h>
+#include <linux/blkdev.h>
+#include <linux/buffer_head.h>
#include <linux/fs_context.h>
#include <linux/gfp_types.h>
#include <linux/stddef.h>
#include "ext4.h"
#include "ext4_extents.h"
+#include "ext4_jbd2.h"
#define EXT_DATA_PBLK 100
#define EXT_DATA_LBLK 10
#define EXT_DATA_LEN 3
+#define EXT_MAX_META_BLOCKS 16
+#define EXT_META_BASE_PBLK 500
+#define EXT_ALLOC_BASE_PBLK 1000
+#define EXT_TOTAL_BLOCKS 0x100000ULL
+
+struct kunit_meta_block {
+ ext4_fsblk_t pblk;
+ struct buffer_head bh;
+ char data[4096];
+ bool in_use;
+};
+
struct kunit_ctx {
/*
- * Ext4 inode which has only 1 unwrit extent
+ * Ext4 inode initialized with 1 extent by default
*/
struct ext4_inode_info *k_ei;
+ /*
+ * Optional secondary inode (used for swap_extents tests)
+ */
+ struct ext4_inode_info *k_ei2;
/*
* Represents the underlying data area (used for zeroout testing)
*/
char *k_data;
+ /*
+ * In-memory pool of external extent tree blocks (leaf/index blocks)
+ */
+ struct kunit_meta_block *meta_blocks;
+ /*
+ * Mock block allocator and free tracking
+ */
+ ext4_fsblk_t next_alloc_pblk;
+ unsigned int alloc_calls;
+ ext4_fsblk_t last_alloc_pblk;
+ unsigned int last_alloc_len;
+ int fail_alloc_err;
+
+ unsigned int free_calls;
+ ext4_fsblk_t last_free_pblk;
+ unsigned long last_free_count;
+ int last_free_flags;
+ unsigned long total_freed_blocks;
+
+ bool fail_getblk;
} k_ctx;
/*
@@ -126,6 +141,31 @@ struct kunit_ext_test_param {
struct kunit_ext_data_state exp_data_state[3];
};
+static unsigned long ext_dummy_shrinker_count(struct shrinker *shrink,
+ struct shrink_control *sc)
+{
+ return 0;
+}
+
+static unsigned long ext_dummy_shrinker_scan(struct shrinker *shrink,
+ struct shrink_control *sc)
+{
+ return SHRINK_STOP;
+}
+
+static const struct super_operations ext_dummy_sops = {};
+
+#ifdef CONFIG_QUOTA
+static qsize_t *ext_get_reserved_space(struct inode *inode)
+{
+ return &EXT4_I(inode)->i_reserved_quota;
+}
+
+static const struct dquot_operations ext_dummy_qops = {
+ .get_reserved_space = ext_get_reserved_space,
+};
+#endif
+
static int ext_init_fs_context(struct fs_context *fc)
{
return 0;
@@ -142,33 +182,201 @@ static struct file_system_type ext_fs_type = {
.kill_sb = kill_anon_super,
};
+static void ext_init_mock_inode(struct ext4_inode_info *ei,
+ struct super_block *sb, unsigned long ino)
+{
+ struct inode *inode = &ei->vfs_inode;
+
+ memset(ei, 0, sizeof(*ei));
+ inode->i_sb = sb;
+ inode->i_ino = ino;
+ inode->i_mode = S_IFREG | 0644;
+ inode->i_blkbits = sb->s_blocksize_bits;
+ inode->i_flags |= S_NOQUOTA;
+ inode->i_mapping = &inode->i_data;
+
+ spin_lock_init(&inode->i_lock);
+ init_rwsem(&inode->i_rwsem);
+ init_rwsem(&inode->i_data.invalidate_lock);
+ inode_state_set_raw(inode, I_DIRTY_SYNC | I_DIRTY_DATASYNC);
+
+ ext4_es_init_tree(&ei->i_es_tree);
+ ext4_init_pending_tree(&ei->i_pending_tree);
+ rwlock_init(&ei->i_es_lock);
+ INIT_LIST_HEAD(&ei->i_es_list);
+ init_rwsem(&ei->i_data_sem);
+ spin_lock_init(&ei->i_block_reservation_lock);
+ rwlock_init(&ei->i_prealloc_lock);
+ ei->i_prealloc_node = RB_ROOT;
+ ei->i_csum_seed = 0x12345678;
+
+ ei->i_disksize = (EXT_DATA_LBLK + EXT_DATA_LEN + 10)
+ << sb->s_blocksize_bits;
+ inode->i_size = ei->i_disksize;
+ ext4_set_inode_flag(inode, EXT4_INODE_EXTENTS);
+}
+
+static void ext_cleanup_inode(struct inode *inode)
+{
+ struct ext4_inode_info *ei = EXT4_I(inode);
+ struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb);
+ struct rb_node *node;
+
+ down_write(&ei->i_data_sem);
+ ext4_es_remove_extent(inode, 0, EXT_MAX_BLOCKS);
+ up_write(&ei->i_data_sem);
+
+ while ((node = rb_first(&ei->i_pending_tree.root)) != NULL) {
+ struct pending_reservation *pr;
+
+ pr = rb_entry(node, struct pending_reservation, rb_node);
+ ext4_remove_pending(inode, EXT4_C2B(sbi, pr->lclu));
+ }
+}
+
static void extents_kunit_exit(struct kunit *test)
{
struct ext4_sb_info *sbi;
+ struct super_block *sb;
if (!k_ctx.k_ei)
return;
- sbi = k_ctx.k_ei->vfs_inode.i_sb->s_fs_info;
+ sb = k_ctx.k_ei->vfs_inode.i_sb;
+ sbi = sb->s_fs_info;
+
+ if (k_ctx.k_ei2) {
+ ext_cleanup_inode(&k_ctx.k_ei2->vfs_inode);
+ kfree(k_ctx.k_ei2);
+ }
+ ext_cleanup_inode(&k_ctx.k_ei->vfs_inode);
+
+ percpu_counter_destroy(&sbi->s_freeclusters_counter);
+ percpu_counter_destroy(&sbi->s_dirtyclusters_counter);
ext4_es_unregister_shrinker(sbi);
- deactivate_super(sbi->s_sb);
+ deactivate_super(sb);
+
+ kfree(k_ctx.meta_blocks);
+ kfree(sbi->s_es);
+ kfree(sb->s_bdev->bd_mapping);
+ kfree(sb->s_bdev);
kfree(sbi);
kfree(k_ctx.k_ei);
kfree(k_ctx.k_data);
+ memset(&k_ctx, 0, sizeof(k_ctx));
}
static int __ext4_ext_dirty_stub(const char *where, unsigned int line,
handle_t *handle, struct inode *inode,
struct ext4_ext_path *path)
{
+ if (path && path->p_bh) {
+ ext4_extent_block_csum_set_test(inode,
+ ext_block_hdr(path->p_bh));
+ set_buffer_verified(path->p_bh);
+ }
return 0;
}
+static int __ext4_handle_dirty_metadata_stub(const char *where,
+ unsigned int line,
+ handle_t *handle,
+ struct inode *inode,
+ struct buffer_head *bh)
+{
+ if (bh)
+ set_buffer_uptodate(bh);
+ return 0;
+}
+
+static int __ext4_mark_inode_dirty_stub(handle_t *handle, struct inode *inode,
+ const char *func, unsigned int line)
+{
+ return 0;
+}
+
+static struct buffer_head *ext4_ext_getblk_stub(struct inode *inode,
+ ext4_fsblk_t pblk,
+ gfp_t gfp_flags)
+{
+ struct kunit_meta_block *slot = NULL;
+ int i;
+
+ if (k_ctx.fail_getblk || !k_ctx.meta_blocks)
+ return NULL;
+
+ for (i = 0; i < EXT_MAX_META_BLOCKS; i++) {
+ if (k_ctx.meta_blocks[i].in_use &&
+ k_ctx.meta_blocks[i].pblk == pblk) {
+ slot = &k_ctx.meta_blocks[i];
+ break;
+ }
+ }
+
+ if (!slot) {
+ for (i = 0; i < EXT_MAX_META_BLOCKS; i++) {
+ if (!k_ctx.meta_blocks[i].in_use) {
+ slot = &k_ctx.meta_blocks[i];
+ break;
+ }
+ }
+ if (!slot)
+ return NULL;
+
+ memset(slot, 0, sizeof(*slot));
+ slot->in_use = true;
+ slot->pblk = pblk;
+ slot->bh.b_data = slot->data;
+ slot->bh.b_size = inode->i_sb->s_blocksize;
+ slot->bh.b_blocknr = pblk;
+ slot->bh.b_bdev = inode->i_sb->s_bdev;
+ atomic_set(&slot->bh.b_count, 1);
+ set_buffer_uptodate(&slot->bh);
+ set_buffer_mapped(&slot->bh);
+ }
+
+ get_bh(&slot->bh);
+ return &slot->bh;
+}
+
+static ext4_fsblk_t ext4_mb_new_blocks_stub(handle_t *handle,
+ struct ext4_allocation_request *ar,
+ int *errp)
+{
+ struct ext4_sb_info *sbi = EXT4_SB(ar->inode->i_sb);
+ ext4_fsblk_t pblk;
+
+ k_ctx.alloc_calls++;
+ if (k_ctx.fail_alloc_err) {
+ *errp = k_ctx.fail_alloc_err;
+ return 0;
+ }
+
+ pblk = k_ctx.next_alloc_pblk;
+ k_ctx.next_alloc_pblk += EXT4_C2B(sbi, ar->len);
+ k_ctx.last_alloc_pblk = pblk;
+ k_ctx.last_alloc_len = ar->len;
+ *errp = 0;
+ return pblk;
+}
+
+static void ext4_free_blocks_stub(handle_t *handle, struct inode *inode,
+ struct buffer_head *bh, ext4_fsblk_t block,
+ unsigned long count, int flags)
+{
+ k_ctx.free_calls++;
+ k_ctx.last_free_pblk = block;
+ k_ctx.last_free_count = count;
+ k_ctx.last_free_flags = flags;
+ k_ctx.total_freed_blocks += count;
+}
+
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)
{
+ ext4_free_ext_path(path);
return ERR_PTR(-ENOSPC);
}
@@ -227,10 +435,11 @@ static int extents_kunit_init(struct kunit *test)
struct super_block *sb;
struct fs_context *fc;
struct ext4_sb_info *sbi = NULL;
- struct kunit_ext_test_param *param =
- (struct kunit_ext_test_param *)(test->param_value);
int err;
+ memset(&k_ctx, 0, sizeof(k_ctx));
+ k_ctx.next_alloc_pblk = EXT_ALLOC_BASE_PBLK;
+
sbi = kzalloc_obj(struct ext4_sb_info);
if (sbi == NULL)
return -ENOMEM;
@@ -249,16 +458,76 @@ static int extents_kunit_init(struct kunit *test)
sbi->s_sb = sb;
sb->s_fs_info = sbi;
-
+ sb->s_op = &ext_dummy_sops;
+#ifdef CONFIG_QUOTA
+ sb->dq_op = &ext_dummy_qops;
+#endif
sb->s_blocksize = 4096;
sb->s_blocksize_bits = 12;
+ sb->s_maxbytes = MAX_LFS_FILESIZE;
+ strscpy(sb->s_id, "extents_kunit", sizeof(sb->s_id));
+
+ sb->s_bdev = kzalloc_obj(struct block_device);
+ if (!sb->s_bdev) {
+ err = -ENOMEM;
+ goto out_deactivate;
+ }
+ bdev_set_flag(sb->s_bdev, BD_READ_ONLY);
+ sb->s_bdev->bd_mapping = kzalloc_obj(struct address_space);
+ if (!sb->s_bdev->bd_mapping) {
+ err = -ENOMEM;
+ goto out_bdev;
+ }
- if (!param || !param->disable_zeroout)
- sbi->s_extent_max_zeroout_kb = 32;
+ sbi->s_es = kzalloc_obj(struct ext4_super_block);
+ if (!sbi->s_es) {
+ err = -ENOMEM;
+ goto out_bdev_mapping;
+ }
+ sbi->s_es->s_first_data_block = cpu_to_le32(1);
+ sbi->s_es->s_rev_level = cpu_to_le32(EXT4_DYNAMIC_REV);
+ ext4_blocks_count_set(sbi->s_es, EXT_TOTAL_BLOCKS);
+ set_opt(sb, ERRORS_CONT);
+ sbi->s_blocks_per_group = 32768;
+ sbi->s_clusters_per_group = 32768;
+ sbi->s_cluster_ratio = 1;
+ sbi->s_cluster_bits = 0;
+ sbi->s_desc_per_block = 64;
+ sbi->s_groups_count = 32;
+ sbi->s_extent_max_zeroout_kb = 32;
+ spin_lock_init(&sbi->s_bdev_wb_lock);
+ spin_lock_init(&sbi->s_error_lock);
+ spin_lock_init(&sbi->s_md_lock);
+ ratelimit_state_init(&sbi->s_warning_ratelimit_state,
+ DEFAULT_RATELIMIT_INTERVAL,
+ DEFAULT_RATELIMIT_BURST);
+
+ err = percpu_counter_init(&sbi->s_freeclusters_counter, 100000,
+ GFP_KERNEL);
+ if (err)
+ goto out_es;
+ err = percpu_counter_init(&sbi->s_dirtyclusters_counter, 0,
+ GFP_KERNEL);
+ if (err)
+ goto out_freeclusters;
err = ext4_es_register_shrinker(sbi);
if (err)
- goto out_deactivate;
+ goto out_dirtyclusters;
+
+ /*
+ * Isolate the mock shrinker from asynchronous background reclaim.
+ */
+ sbi->s_es_shrinker->count_objects = ext_dummy_shrinker_count;
+ sbi->s_es_shrinker->scan_objects = ext_dummy_shrinker_scan;
+
+ k_ctx.meta_blocks = kcalloc(EXT_MAX_META_BLOCKS,
+ sizeof(struct kunit_meta_block),
+ GFP_KERNEL);
+ if (!k_ctx.meta_blocks) {
+ err = -ENOMEM;
+ goto out;
+ }
/* setup the mock inode */
k_ctx.k_ei = kzalloc_obj(struct ext4_inode_info);
@@ -268,19 +537,7 @@ static int extents_kunit_init(struct kunit *test)
}
ei = k_ctx.k_ei;
inode = &ei->vfs_inode;
-
- 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 = 0;
- ei->i_es_shk_nr = 0;
- ei->i_es_shrink_lblk = 0;
-
- ei->i_disksize = (EXT_DATA_LBLK + EXT_DATA_LEN + 10)
- << sb->s_blocksize_bits;
- ei->i_flags = 0;
- ext4_set_inode_flag(inode, EXT4_INODE_EXTENTS);
- inode->i_sb = sb;
+ ext_init_mock_inode(ei, sb, 12);
k_ctx.k_data = kzalloc(EXT_DATA_LEN * 4096, GFP_KERNEL);
if (k_ctx.k_data == NULL) {
@@ -293,9 +550,6 @@ static int extents_kunit_init(struct kunit *test)
*/
memset(k_ctx.k_data, 'X', EXT_DATA_LEN * 4096);
- /* create a tree with depth 0 */
- eh = (struct ext4_extent_header *)k_ctx.k_ei->i_data;
-
/* Fill extent header */
eh = ext_inode_hdr(&k_ctx.k_ei->vfs_inode);
eh->eh_depth = 0;
@@ -307,25 +561,34 @@ static int extents_kunit_init(struct kunit *test)
/*
* add 1 extent in leaf node covering:
- * - lblks: [EXT_DATA_LBLK, EXT_DATA_LBLK * + EXT_DATA_LEN)
+ * - 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 = cpu_to_le32(EXT_DATA_LBLK);
EXT_FIRST_EXTENT(eh)->ee_len = 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));
+ ext4_ext_mark_unwritten(EXT_FIRST_EXTENT(eh));
+ down_write(&ei->i_data_sem);
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);
+ EXTENT_STATUS_UNWRITTEN, 0);
+ up_write(&ei->i_data_sem);
/* Add stubs */
kunit_activate_static_stub(test, __ext4_ext_dirty,
__ext4_ext_dirty_stub);
- kunit_activate_static_stub(test, ext4_ext_zeroout, ext4_ext_zeroout_stub);
+ kunit_activate_static_stub(test, __ext4_handle_dirty_metadata,
+ __ext4_handle_dirty_metadata_stub);
+ kunit_activate_static_stub(test, __ext4_mark_inode_dirty,
+ __ext4_mark_inode_dirty_stub);
+ kunit_activate_static_stub(test, ext4_ext_getblk,
+ ext4_ext_getblk_stub);
+ kunit_activate_static_stub(test, ext4_mb_new_blocks,
+ ext4_mb_new_blocks_stub);
+ kunit_activate_static_stub(test, ext4_free_blocks,
+ ext4_free_blocks_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);
up_write(&sb->s_umount);
@@ -333,13 +596,23 @@ static int extents_kunit_init(struct kunit *test)
return 0;
out:
- kfree(k_ctx.k_ei);
- k_ctx.k_ei = NULL;
-
kfree(k_ctx.k_data);
k_ctx.k_data = NULL;
-
+ kfree(k_ctx.k_ei);
+ k_ctx.k_ei = NULL;
+ kfree(k_ctx.meta_blocks);
+ k_ctx.meta_blocks = NULL;
ext4_es_unregister_shrinker(sbi);
+out_dirtyclusters:
+ percpu_counter_destroy(&sbi->s_dirtyclusters_counter);
+out_freeclusters:
+ percpu_counter_destroy(&sbi->s_freeclusters_counter);
+out_es:
+ kfree(sbi->s_es);
+out_bdev_mapping:
+ kfree(sb->s_bdev->bd_mapping);
+out_bdev:
+ kfree(sb->s_bdev);
out_deactivate:
deactivate_locked_super(sb);
kfree(sbi);
@@ -392,12 +665,24 @@ static void test_split_convert(struct kunit *test)
{
struct ext4_ext_path *path;
struct inode *inode = &k_ctx.k_ei->vfs_inode;
+ struct ext4_extent_header *eh = ext_inode_hdr(inode);
struct ext4_extent *ex;
struct ext4_map_blocks map;
const struct kunit_ext_test_param *param =
(const struct kunit_ext_test_param *)(test->param_value);
int blkbits = inode->i_sb->s_blocksize_bits;
+ if (param->disable_zeroout)
+ EXT4_SB(inode->i_sb)->s_extent_max_zeroout_kb = 0;
+
+ if (!param->is_unwrit_at_start) {
+ ext4_ext_mark_initialized(EXT_FIRST_EXTENT(eh));
+ down_write(&EXT4_I(inode)->i_data_sem);
+ ext4_es_insert_extent(inode, EXT_DATA_LBLK, EXT_DATA_LEN,
+ EXT_DATA_PBLK, EXTENT_STATUS_WRITTEN, 0);
+ up_write(&EXT4_I(inode)->i_data_sem);
+ }
+
if (param->is_zeroout_test)
/*
* Force zeroout by making ext4_ext_insert_extent return ENOSPC
@@ -406,6 +691,7 @@ static void test_split_convert(struct kunit *test)
ext4_ext_insert_extent_stub);
path = ext4_find_extent(inode, EXT_DATA_LBLK, NULL, EXT4_EX_NOCACHE);
+ KUNIT_ASSERT_FALSE(test, IS_ERR_OR_NULL(path));
ex = 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));
@@ -423,15 +709,21 @@ static void test_split_convert(struct kunit *test)
case TEST_SPLIT_CONVERT:
path = ext4_split_convert_extents_test(NULL, inode, &map,
path, param->split_flags, NULL);
+ if (!IS_ERR(path))
+ ext4_free_ext_path(path);
break;
case TEST_CREATE_BLOCKS:
+ ext4_free_ext_path(path);
ext4_map_create_blocks_helper(test, inode, &map, param->split_flags);
break;
default:
+ ext4_free_ext_path(path);
KUNIT_FAIL(test, "param->type %d not support.", param->type);
+ return;
}
path = ext4_find_extent(inode, EXT_DATA_LBLK, NULL, EXT4_EX_NOCACHE);
+ KUNIT_ASSERT_FALSE(test, IS_ERR_OR_NULL(path));
ex = path->p_ext;
for (int i = 0; i < param->nr_exp_ext; i++) {
@@ -494,6 +786,8 @@ static void test_split_convert(struct kunit *test)
ex = ex + 1;
}
+ ext4_free_ext_path(path);
+
if (!param->is_zeroout_test)
return;
@@ -512,8 +806,1439 @@ static void test_split_convert(struct kunit *test)
"# corruption in byte range [%lld, %lld)",
off, len);
}
+}
+
+/*
+ * Helper to reset an inode's extent tree and extent status cache to empty.
+ */
+static void ext_reset_tree(struct inode *inode)
+{
+ struct ext4_inode_info *ei = EXT4_I(inode);
+
+ down_write(&ei->i_data_sem);
+ ext4_es_remove_extent(inode, 0, EXT_MAX_BLOCKS);
+ memset(ei->i_data, 0, sizeof(ei->i_data));
+ ext4_ext_tree_init(NULL, inode);
+ up_write(&ei->i_data_sem);
+}
+
+/*
+ * Helper to insert an extent into @inode's extent tree under i_data_sem.
+ */
+static int ext_insert_one(struct inode *inode, ext4_lblk_t lblk,
+ unsigned short len, ext4_fsblk_t pblk,
+ bool unwritten, int gb_flags)
+{
+ struct ext4_ext_path *path;
+ struct ext4_extent newex = {};
+
+ newex.ee_block = cpu_to_le32(lblk);
+ newex.ee_len = cpu_to_le16(len);
+ ext4_ext_store_pblock(&newex, pblk);
+ if (unwritten)
+ ext4_ext_mark_unwritten(&newex);
+
+ down_write(&EXT4_I(inode)->i_data_sem);
+ path = ext4_find_extent(inode, lblk, NULL, EXT4_EX_NOCACHE);
+ if (IS_ERR(path)) {
+ up_write(&EXT4_I(inode)->i_data_sem);
+ return PTR_ERR(path);
+ }
+ path = ext4_ext_insert_extent(NULL, inode, path, &newex,
+ gb_flags | EXT4_EX_NOCACHE);
+ up_write(&EXT4_I(inode)->i_data_sem);
+ if (IS_ERR(path))
+ return PTR_ERR(path);
+
+ ext4_free_ext_path(path);
+ return 0;
+}
+
+/*
+ * Verify 48-bit physical block number store and load helpers for both
+ * leaf extents (ext4_extent) and index entries (ext4_extent_idx).
+ */
+static void test_ext_pblock_48bit(struct kunit *test)
+{
+ struct ext4_extent ex = {};
+ struct ext4_extent_idx idx = {};
+ const ext4_fsblk_t pblk_48 = 0x123456789abcULL;
+ const ext4_fsblk_t pblk_max = 0xffffffffffffULL;
+
+ ext4_ext_store_pblock(&ex, 0);
+ KUNIT_EXPECT_EQ(test, ext4_ext_pblock(&ex), 0ULL);
+
+ ext4_ext_store_pblock(&ex, 0xffffffffULL);
+ KUNIT_EXPECT_EQ(test, ext4_ext_pblock(&ex), 0xffffffffULL);
+ KUNIT_EXPECT_EQ(test, le16_to_cpu(ex.ee_start_hi), 0);
+
+ ext4_ext_store_pblock(&ex, pblk_48);
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(ex.ee_start_lo), 0x56789abcU);
+ KUNIT_EXPECT_EQ(test, le16_to_cpu(ex.ee_start_hi), 0x1234U);
+ KUNIT_EXPECT_EQ(test, ext4_ext_pblock(&ex), pblk_48);
+
+ ext4_ext_store_pblock(&ex, pblk_max);
+ KUNIT_EXPECT_EQ(test, ext4_ext_pblock(&ex), pblk_max);
+
+ ext4_idx_store_pblock(&idx, pblk_48);
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(idx.ei_leaf_lo), 0x56789abcU);
+ KUNIT_EXPECT_EQ(test, le16_to_cpu(idx.ei_leaf_hi), 0x1234U);
+ KUNIT_EXPECT_EQ(test, ext4_idx_pblock(&idx), pblk_48);
+
+ ext4_idx_store_pblock(&idx, pblk_max);
+ KUNIT_EXPECT_EQ(test, ext4_idx_pblock(&idx), pblk_max);
+}
+
+/*
+ * Verify extent length encoding and unwritten bit manipulation at boundaries:
+ * - EXT_INIT_MAX_LEN (32768 = 0x8000) is the maximum initialized extent length
+ * and must NOT be treated as unwritten.
+ * - EXT_UNWRITTEN_MAX_LEN (32767 = 0x7fff) is the maximum unwritten extent
+ * length (stored on disk as 0xffff).
+ */
+static void test_ext_unwritten_len_encoding(struct kunit *test)
+{
+ struct ext4_extent ex = {};
+
+ ex.ee_len = cpu_to_le16(1);
+ KUNIT_EXPECT_EQ(test, ext4_ext_is_unwritten(&ex), 0);
+ KUNIT_EXPECT_EQ(test, ext4_ext_get_actual_len(&ex), 1);
+
+ ext4_ext_mark_unwritten(&ex);
+ KUNIT_EXPECT_EQ(test, ext4_ext_is_unwritten(&ex), 1);
+ KUNIT_EXPECT_EQ(test, ext4_ext_get_actual_len(&ex), 1);
+
+ ext4_ext_mark_initialized(&ex);
+ KUNIT_EXPECT_EQ(test, ext4_ext_is_unwritten(&ex), 0);
+ KUNIT_EXPECT_EQ(test, ext4_ext_get_actual_len(&ex), 1);
+
+ ex.ee_len = cpu_to_le16(EXT_UNWRITTEN_MAX_LEN);
+ ext4_ext_mark_unwritten(&ex);
+ KUNIT_EXPECT_EQ(test, ext4_ext_is_unwritten(&ex), 1);
+ KUNIT_EXPECT_EQ(test, ext4_ext_get_actual_len(&ex),
+ EXT_UNWRITTEN_MAX_LEN);
+
+ ext4_ext_mark_initialized(&ex);
+ KUNIT_EXPECT_EQ(test, ext4_ext_is_unwritten(&ex), 0);
+ KUNIT_EXPECT_EQ(test, ext4_ext_get_actual_len(&ex),
+ EXT_UNWRITTEN_MAX_LEN);
+
+ ex.ee_len = cpu_to_le16(EXT_INIT_MAX_LEN);
+ KUNIT_EXPECT_EQ(test, ext4_ext_is_unwritten(&ex), 0);
+ KUNIT_EXPECT_EQ(test, ext4_ext_get_actual_len(&ex), EXT_INIT_MAX_LEN);
+}
+
+/*
+ * Verify ext4_ext_tree_init() initializes the in-inode extent header and
+ * ext4_ext_check_inode() accepts valid empty and populated root headers.
+ */
+static void test_ext_tree_init_and_check_inode(struct kunit *test)
+{
+ struct inode *inode = &k_ctx.k_ei->vfs_inode;
+ struct ext4_extent_header *eh = ext_inode_hdr(inode);
+
+ memset(k_ctx.k_ei->i_data, 0xff, sizeof(k_ctx.k_ei->i_data));
+ ext4_ext_tree_init(NULL, inode);
+
+ KUNIT_EXPECT_EQ(test, eh->eh_magic, EXT4_EXT_MAGIC);
+ KUNIT_EXPECT_EQ(test, le16_to_cpu(eh->eh_depth), 0);
+ KUNIT_EXPECT_EQ(test, le16_to_cpu(eh->eh_entries), 0);
+ KUNIT_EXPECT_EQ(test, le16_to_cpu(eh->eh_max), 4);
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(eh->eh_generation), 0U);
+ KUNIT_EXPECT_EQ(test, ext4_ext_check_inode(inode), 0);
+
+ KUNIT_ASSERT_EQ(test, ext_insert_one(inode, 10, 5, 100, false, 0), 0);
+ KUNIT_EXPECT_EQ(test, ext4_ext_check_inode(inode), 0);
+}
+
+enum ext_check_corrupt_type {
+ CORRUPT_BAD_MAGIC,
+ CORRUPT_BAD_DEPTH,
+ CORRUPT_ZERO_MAX,
+ CORRUPT_EXCESS_MAX,
+ CORRUPT_ENTRIES_GT_MAX,
+ CORRUPT_EMPTY_NONROOT_INDEX,
+ CORRUPT_ZERO_LEN_EXTENT,
+ CORRUPT_LBLK_OVERFLOW,
+ CORRUPT_PBLK_TOO_LOW,
+ CORRUPT_PBLK_TOO_HIGH,
+ CORRUPT_UNSORTED_EXTENTS,
+ CORRUPT_UNSORTED_INDEXES,
+ CORRUPT_NONROOT_LBLK_MISMATCH,
+};
+
+struct ext_check_corrupt_param {
+ const char *desc;
+ enum ext_check_corrupt_type type;
+};
+
+static const struct ext_check_corrupt_param ext_check_corrupt_params[] = {
+ { "invalid eh_magic", CORRUPT_BAD_MAGIC },
+ { "mismatched eh_depth", CORRUPT_BAD_DEPTH },
+ { "eh_max == 0", CORRUPT_ZERO_MAX },
+ { "eh_max exceeds root capacity", CORRUPT_EXCESS_MAX },
+ { "eh_entries exceeds eh_max", CORRUPT_ENTRIES_GT_MAX },
+ { "empty index block at depth > 0", CORRUPT_EMPTY_NONROOT_INDEX },
+ { "extent with ee_len == 0", CORRUPT_ZERO_LEN_EXTENT },
+ { "extent logical block 32-bit wrap-around", CORRUPT_LBLK_OVERFLOW },
+ { "extent physical block <= first_data_block", CORRUPT_PBLK_TOO_LOW },
+ { "extent physical block past blocks_count", CORRUPT_PBLK_TOO_HIGH },
+ { "overlapping/unsorted leaf extents", CORRUPT_UNSORTED_EXTENTS },
+ { "unsorted index entries", CORRUPT_UNSORTED_INDEXES },
+ { "non-root leaf first ee_block != parent ei_block",
+ CORRUPT_NONROOT_LBLK_MISMATCH },
+};
+
+static void ext_check_corrupt_get_desc(struct kunit *test, const void *p,
+ char *desc)
+{
+ const struct ext_check_corrupt_param *param = p;
+
+ strscpy(desc, param->desc, KUNIT_PARAM_DESC_SIZE);
+}
+
+static int ext_check_corrupt_param_init(struct kunit *test)
+{
+ kunit_register_params_array(test, ext_check_corrupt_params,
+ ARRAY_SIZE(ext_check_corrupt_params),
+ ext_check_corrupt_get_desc);
+ return 0;
+}
+
+/*
+ * Verify __ext4_ext_check() detects and rejects all classes of corrupt
+ * extent headers, leaf extents, and index entries with -EFSCORRUPTED.
+ */
+static void test_ext_check_corruptions(struct kunit *test)
+{
+ const struct ext_check_corrupt_param *param = test->param_value;
+ struct inode *inode = &k_ctx.k_ei->vfs_inode;
+ struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb);
+ struct ext4_extent_header *eh = ext_inode_hdr(inode);
+ struct ext4_extent *ex;
+ struct ext4_extent_idx *ix;
+ struct {
+ struct ext4_extent_header eh;
+ struct ext4_extent ex;
+ } nonroot_leaf;
+ int depth = 0;
+ ext4_fsblk_t pblk = 0;
+ ext4_lblk_t lblk = 0;
+ int err;
+
+ ext_reset_tree(inode);
+ eh->eh_entries = cpu_to_le16(1);
+ ex = EXT_FIRST_EXTENT(eh);
+ ex->ee_block = cpu_to_le32(10);
+ ex->ee_len = cpu_to_le16(4);
+ ext4_ext_store_pblock(ex, 100);
+
+ switch (param->type) {
+ case CORRUPT_BAD_MAGIC:
+ eh->eh_magic = cpu_to_le16(0xdead);
+ break;
+ case CORRUPT_BAD_DEPTH:
+ eh->eh_depth = cpu_to_le16(1);
+ depth = 0;
+ break;
+ case CORRUPT_ZERO_MAX:
+ eh->eh_max = 0;
+ break;
+ case CORRUPT_EXCESS_MAX:
+ eh->eh_max = cpu_to_le16(10);
+ break;
+ case CORRUPT_ENTRIES_GT_MAX:
+ eh->eh_entries = cpu_to_le16(5);
+ break;
+ case CORRUPT_EMPTY_NONROOT_INDEX:
+ eh->eh_depth = cpu_to_le16(1);
+ eh->eh_entries = 0;
+ depth = 1;
+ break;
+ case CORRUPT_ZERO_LEN_EXTENT:
+ ex->ee_len = 0;
+ break;
+ case CORRUPT_LBLK_OVERFLOW:
+ ex->ee_block = cpu_to_le32(0xfffffff0U);
+ ex->ee_len = cpu_to_le16(32);
+ break;
+ case CORRUPT_PBLK_TOO_LOW:
+ ext4_ext_store_pblock(ex, 1);
+ break;
+ case CORRUPT_PBLK_TOO_HIGH:
+ ext4_ext_store_pblock(ex, EXT_TOTAL_BLOCKS);
+ break;
+ case CORRUPT_UNSORTED_EXTENTS:
+ eh->eh_entries = cpu_to_le16(2);
+ ex[1].ee_block = cpu_to_le32(12);
+ ex[1].ee_len = cpu_to_le16(4);
+ ext4_ext_store_pblock(&ex[1], 200);
+ break;
+ case CORRUPT_UNSORTED_INDEXES:
+ eh->eh_depth = cpu_to_le16(1);
+ eh->eh_entries = cpu_to_le16(2);
+ depth = 1;
+ ix = EXT_FIRST_INDEX(eh);
+ ix[0].ei_block = cpu_to_le32(20);
+ ext4_idx_store_pblock(&ix[0], 500);
+ ix[1].ei_block = cpu_to_le32(10);
+ ext4_idx_store_pblock(&ix[1], 501);
+ break;
+ case CORRUPT_NONROOT_LBLK_MISMATCH:
+ eh->eh_depth = cpu_to_le16(1);
+ memset(&nonroot_leaf, 0, sizeof(nonroot_leaf));
+ nonroot_leaf.eh.eh_magic = EXT4_EXT_MAGIC;
+ nonroot_leaf.eh.eh_depth = 0;
+ nonroot_leaf.eh.eh_entries = cpu_to_le16(1);
+ nonroot_leaf.eh.eh_max = cpu_to_le16(340);
+ nonroot_leaf.ex.ee_block = cpu_to_le32(10);
+ nonroot_leaf.ex.ee_len = cpu_to_le16(4);
+ ext4_ext_store_pblock(&nonroot_leaf.ex, 100);
+ eh = &nonroot_leaf.eh;
+ depth = 0;
+ pblk = 500;
+ lblk = 20;
+ break;
+ }
+
+ /* Suppress __ext4_error_inode() log spam during expected failure */
+ set_bit(EXT4_FLAGS_SHUTDOWN, &sbi->s_ext4_flags);
+ err = ext4_ext_check_test(inode, eh, depth, pblk, lblk);
+ clear_bit(EXT4_FLAGS_SHUTDOWN, &sbi->s_ext4_flags);
+
+ KUNIT_EXPECT_EQ(test, err, -EFSCORRUPTED);
+}
+
+/*
+ * Verify metadata checksum calculation and verification on external extent
+ * tree blocks when EXT4_FEATURE_RO_COMPAT_METADATA_CSUM is enabled.
+ */
+static void test_ext_block_csum_verify(struct kunit *test)
+{
+ struct inode *inode = &k_ctx.k_ei->vfs_inode;
+ struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb);
+ struct buffer_head *bh;
+ struct ext4_extent_header *eh;
+ struct ext4_extent *ex;
+ struct ext4_extent_tail *et;
+ int max_entries;
+ int err;
+
+ ext_reset_tree(inode);
+ ext_inode_hdr(inode)->eh_depth = cpu_to_le16(1);
+ ext4_set_feature_metadata_csum(inode->i_sb);
+ bh = ext4_ext_getblk_stub(inode, EXT_META_BASE_PBLK, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, bh);
+
+ max_entries = (inode->i_sb->s_blocksize -
+ sizeof(struct ext4_extent_header) -
+ sizeof(struct ext4_extent_tail)) /
+ sizeof(struct ext4_extent);
+
+ eh = ext_block_hdr(bh);
+ eh->eh_magic = EXT4_EXT_MAGIC;
+ eh->eh_depth = 0;
+ eh->eh_entries = cpu_to_le16(1);
+ eh->eh_max = cpu_to_le16(max_entries);
+
+ ex = EXT_FIRST_EXTENT(eh);
+ ex->ee_block = cpu_to_le32(10);
+ ex->ee_len = cpu_to_le16(5);
+ ext4_ext_store_pblock(ex, 100);
+
+ ext4_extent_block_csum_set_test(inode, eh);
+ err = ext4_ext_check_test(inode, eh, 0, EXT_META_BASE_PBLK, 10);
+ KUNIT_EXPECT_EQ(test, err, 0);
+
+ /* Flip a bit in the tail checksum and verify -EFSBADCRC */
+ et = find_ext4_extent_tail(eh);
+ et->et_checksum ^= cpu_to_le32(0x1);
+
+ set_bit(EXT4_FLAGS_SHUTDOWN, &sbi->s_ext4_flags);
+ err = ext4_ext_check_test(inode, eh, 0, EXT_META_BASE_PBLK, 10);
+ clear_bit(EXT4_FLAGS_SHUTDOWN, &sbi->s_ext4_flags);
+
+ KUNIT_EXPECT_EQ(test, err, -EFSBADCRC);
+ brelse(bh);
+}
+
+/*
+ * Verify ext4_find_extent() binary search on an empty tree and a multi-extent
+ * root leaf, as well as extent status caching vs EXT4_EX_NOCACHE.
+ */
+static void test_find_extent_root_binsearch(struct kunit *test)
+{
+ struct inode *inode = &k_ctx.k_ei->vfs_inode;
+ struct ext4_ext_path *path;
+ struct extent_status es;
+
+ ext_reset_tree(inode);
+
+ /* Empty tree returns NULL p_ext */
+ path = ext4_find_extent(inode, 10, NULL, EXT4_EX_NOCACHE);
+ KUNIT_ASSERT_FALSE(test, IS_ERR_OR_NULL(path));
+ KUNIT_EXPECT_PTR_EQ(test, path[0].p_ext, NULL);
+ ext4_free_ext_path(path);
+
+ /* Populate 3 separated extents: [10, 15), [20, 25), [30, 35) */
+ KUNIT_ASSERT_EQ(test, ext_insert_one(inode, 10, 5, 100, false, 0), 0);
+ KUNIT_ASSERT_EQ(test, ext_insert_one(inode, 20, 5, 200, false, 0), 0);
+ KUNIT_ASSERT_EQ(test, ext_insert_one(inode, 30, 5, 300, false, 0), 0);
+
+ /* Lookup before first extent (lblk 5) -> points to first extent */
+ path = ext4_find_extent(inode, 5, NULL, EXT4_EX_NOCACHE);
+ KUNIT_ASSERT_FALSE(test, IS_ERR_OR_NULL(path));
+ KUNIT_ASSERT_NOT_NULL(test, path[0].p_ext);
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(path[0].p_ext->ee_block), 10U);
+ ext4_free_ext_path(path);
+
+ /* Lookup inside first extent (lblk 12) */
+ path = ext4_find_extent(inode, 12, NULL, EXT4_EX_NOCACHE);
+ KUNIT_ASSERT_FALSE(test, IS_ERR_OR_NULL(path));
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(path[0].p_ext->ee_block), 10U);
+ ext4_free_ext_path(path);
+
+ /* Lookup in hole between 1st and 2nd extent (lblk 17) -> 1st extent */
+ path = ext4_find_extent(inode, 17, NULL, EXT4_EX_NOCACHE);
+ KUNIT_ASSERT_FALSE(test, IS_ERR_OR_NULL(path));
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(path[0].p_ext->ee_block), 10U);
+ ext4_free_ext_path(path);
+
+ /* Lookup exact start of 2nd extent (lblk 20) */
+ path = ext4_find_extent(inode, 20, NULL, EXT4_EX_NOCACHE);
+ KUNIT_ASSERT_FALSE(test, IS_ERR_OR_NULL(path));
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(path[0].p_ext->ee_block), 20U);
+ ext4_free_ext_path(path);
+
+ /* Lookup past last extent (lblk 50) -> 3rd extent */
+ path = ext4_find_extent(inode, 50, NULL, EXT4_EX_NOCACHE);
+ KUNIT_ASSERT_FALSE(test, IS_ERR_OR_NULL(path));
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(path[0].p_ext->ee_block), 30U);
+ ext4_free_ext_path(path);
+
+ /* EXT4_EX_NOCACHE left i_es_tree empty */
+ KUNIT_EXPECT_EQ(test, ext4_es_lookup_extent(inode, 10, NULL, &es, NULL),
+ 0);
+
+ /* Lookup without EXT4_EX_NOCACHE populates extents and holes in es */
+ down_read(&EXT4_I(inode)->i_data_sem);
+ path = ext4_find_extent(inode, 20, NULL, 0);
+ up_read(&EXT4_I(inode)->i_data_sem);
+ KUNIT_ASSERT_FALSE(test, IS_ERR_OR_NULL(path));
+ ext4_free_ext_path(path);
+
+ /*
+ * Verify inter-extent hole [15, 20) and extents are cached in es
+ * (ext4_cache_extents only caches holes where prev != 0).
+ */
+ KUNIT_EXPECT_EQ(test, ext4_es_lookup_extent(inode, 5, NULL, &es, NULL),
+ 0);
+ KUNIT_EXPECT_EQ(test, ext4_es_lookup_extent(inode, 17, NULL, &es, NULL),
+ 1);
+ KUNIT_EXPECT_TRUE(test, ext4_es_is_hole(&es));
+ KUNIT_EXPECT_EQ(test, es.es_lblk, 15U);
+ KUNIT_EXPECT_EQ(test, es.es_len, 5U);
+
+ KUNIT_EXPECT_EQ(test, ext4_es_lookup_extent(inode, 22, NULL, &es, NULL),
+ 1);
+ KUNIT_EXPECT_TRUE(test, ext4_es_is_written(&es));
+ KUNIT_EXPECT_EQ(test, ext4_es_pblock(&es), 200ULL);
+}
+
+/*
+ * Helper to construct a depth-1 tree with two external leaf blocks:
+ * Leaf 0 (pblk 500): [10, 15) -> 1000 (written), [20, 25) -> 1100 (written)
+ * Leaf 1 (pblk 501): [40, 45) -> 2000 (unwritten), [50, 55) -> 2100 (written)
+ */
+static void setup_depth1_two_leaf_tree(struct kunit *test, struct inode *inode)
+{
+ struct ext4_extent_header *root_eh = ext_inode_hdr(inode);
+ struct ext4_extent_idx *idx;
+ struct buffer_head *bh0, *bh1;
+ struct ext4_extent_header *eh0, *eh1;
+ struct ext4_extent *ex;
+ int max_leaf = (inode->i_sb->s_blocksize -
+ sizeof(struct ext4_extent_header)) /
+ sizeof(struct ext4_extent);
+
+ ext_reset_tree(inode);
+
+ root_eh->eh_depth = cpu_to_le16(1);
+ root_eh->eh_entries = cpu_to_le16(2);
+ root_eh->eh_max = cpu_to_le16(ext4_ext_space_root_idx_test(inode, 0));
+
+ idx = EXT_FIRST_INDEX(root_eh);
+ idx[0].ei_block = cpu_to_le32(10);
+ ext4_idx_store_pblock(&idx[0], EXT_META_BASE_PBLK);
+ idx[1].ei_block = cpu_to_le32(40);
+ ext4_idx_store_pblock(&idx[1], EXT_META_BASE_PBLK + 1);
+
+ bh0 = ext4_ext_getblk_stub(inode, EXT_META_BASE_PBLK, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, bh0);
+ eh0 = ext_block_hdr(bh0);
+ eh0->eh_magic = EXT4_EXT_MAGIC;
+ eh0->eh_depth = 0;
+ eh0->eh_entries = cpu_to_le16(2);
+ eh0->eh_max = cpu_to_le16(max_leaf);
+ ex = EXT_FIRST_EXTENT(eh0);
+ ex[0].ee_block = cpu_to_le32(10);
+ ex[0].ee_len = cpu_to_le16(5);
+ ext4_ext_store_pblock(&ex[0], 1000);
+ ex[1].ee_block = cpu_to_le32(20);
+ ex[1].ee_len = cpu_to_le16(5);
+ ext4_ext_store_pblock(&ex[1], 1100);
+ brelse(bh0);
+
+ bh1 = ext4_ext_getblk_stub(inode, EXT_META_BASE_PBLK + 1, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, bh1);
+ eh1 = ext_block_hdr(bh1);
+ eh1->eh_magic = EXT4_EXT_MAGIC;
+ eh1->eh_depth = 0;
+ eh1->eh_entries = cpu_to_le16(2);
+ eh1->eh_max = cpu_to_le16(max_leaf);
+ ex = EXT_FIRST_EXTENT(eh1);
+ ex[0].ee_block = cpu_to_le32(40);
+ ex[0].ee_len = cpu_to_le16(5);
+ ext4_ext_store_pblock(&ex[0], 2000);
+ ext4_ext_mark_unwritten(&ex[0]);
+ ex[1].ee_block = cpu_to_le32(50);
+ ex[1].ee_len = cpu_to_le16(5);
+ ext4_ext_store_pblock(&ex[1], 2100);
+ brelse(bh1);
+}
+
+/*
+ * Verify multi-level (depth 1) extent lookup and tree navigation helpers:
+ * ext4_ext_next_allocated_block(), ext4_ext_next_leaf_block(),
+ * ext4_ext_search_left(), and ext4_ext_search_right() across leaf boundaries.
+ */
+static void test_find_extent_multilevel_and_navigation(struct kunit *test)
+{
+ struct inode *inode = &k_ctx.k_ei->vfs_inode;
+ struct ext4_ext_path *path;
+ struct ext4_extent ret_ex;
+ ext4_lblk_t logical;
+ ext4_fsblk_t phys;
+ int ret;
+
+ setup_depth1_two_leaf_tree(test, inode);
+
+ /* Lookup first extent [10, 15) in leaf 0 */
+ path = ext4_find_extent(inode, 10, NULL, EXT4_EX_NOCACHE);
+ KUNIT_ASSERT_FALSE(test, IS_ERR_OR_NULL(path));
+ KUNIT_EXPECT_EQ(test, path->p_depth, 1);
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(path[1].p_ext->ee_block), 10U);
+ KUNIT_EXPECT_EQ(test, ext4_ext_next_allocated_block(path), 20U);
+ KUNIT_EXPECT_EQ(test, ext4_ext_next_leaf_block_test(path), 40U);
+ ext4_free_ext_path(path);
+
+ /*
+ * Lookup lblk 30 (in hole between leaf 0 and leaf 1):
+ * path[1].p_ext points to [20, 25) (last extent in leaf 0).
+ */
+ path = ext4_find_extent(inode, 30, NULL, EXT4_EX_NOCACHE);
+ KUNIT_ASSERT_FALSE(test, IS_ERR_OR_NULL(path));
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(path[1].p_ext->ee_block), 20U);
+ KUNIT_EXPECT_EQ(test, ext4_ext_next_allocated_block(path), 40U);
+
+ /* Search left from lblk 30 finds last block of [20, 25) -> 24 @ 1104 */
+ logical = 30;
+ ret = ext4_ext_search_left_test(inode, path, &logical, &phys);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, logical, 24U);
+ KUNIT_EXPECT_EQ(test, phys, 1104ULL);
+
+ /* Search right from lblk 30 crosses into leaf 1 -> [40, 45) @ 2000 */
+ logical = 30;
+ ret = ext4_ext_search_right_test(inode, path, &logical, &phys, &ret_ex,
+ EXT4_EX_NOCACHE);
+ KUNIT_EXPECT_EQ(test, ret, 1);
+ KUNIT_EXPECT_EQ(test, logical, 40U);
+ KUNIT_EXPECT_EQ(test, phys, 2000ULL);
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(ret_ex.ee_block), 40U);
+ ext4_free_ext_path(path);
+
+ /* Lookup last extent [50, 55) in leaf 1 */
+ path = ext4_find_extent(inode, 52, NULL, EXT4_EX_NOCACHE);
+ KUNIT_ASSERT_FALSE(test, IS_ERR_OR_NULL(path));
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(path[1].p_ext->ee_block), 50U);
+ KUNIT_EXPECT_EQ(test, ext4_ext_next_allocated_block(path),
+ EXT_MAX_BLOCKS);
+ KUNIT_EXPECT_EQ(test, ext4_ext_next_leaf_block_test(path),
+ EXT_MAX_BLOCKS);
+ ext4_free_ext_path(path);
+}
+
+/*
+ * Verify ext4_ext_precache() walks all leaf blocks of a multi-level extent
+ * tree, caches all extents and holes into the extent status tree, and sets
+ * EXT4_STATE_EXT_PRECACHED.
+ */
+static void test_ext_precache(struct kunit *test)
+{
+ struct inode *inode = &k_ctx.k_ei->vfs_inode;
+ struct extent_status es;
+ int err;
+
+ setup_depth1_two_leaf_tree(test, inode);
+ KUNIT_EXPECT_FALSE(test,
+ ext4_test_inode_state(inode,
+ EXT4_STATE_EXT_PRECACHED));
+
+ err = ext4_ext_precache(inode);
+ KUNIT_EXPECT_EQ(test, err, 0);
+ KUNIT_EXPECT_TRUE(test,
+ ext4_test_inode_state(inode,
+ EXT4_STATE_EXT_PRECACHED));
+
+ /* Verify cached inter-extent hole [15, 20) and extents from both leaves */
+ KUNIT_EXPECT_EQ(test, ext4_es_lookup_extent(inode, 17, NULL, &es, NULL),
+ 1);
+ KUNIT_EXPECT_TRUE(test, ext4_es_is_hole(&es));
+ KUNIT_EXPECT_EQ(test, es.es_lblk, 15U);
+ KUNIT_EXPECT_EQ(test, es.es_len, 5U);
+
+ KUNIT_EXPECT_EQ(test, ext4_es_lookup_extent(inode, 10, NULL, &es, NULL),
+ 1);
+ KUNIT_EXPECT_TRUE(test, ext4_es_is_written(&es));
+ KUNIT_EXPECT_EQ(test, ext4_es_pblock(&es), 1000ULL);
+
+ KUNIT_EXPECT_EQ(test, ext4_es_lookup_extent(inode, 40, NULL, &es, NULL),
+ 1);
+ KUNIT_EXPECT_TRUE(test, ext4_es_is_unwritten(&es));
+ KUNIT_EXPECT_EQ(test, ext4_es_pblock(&es), 2000ULL);
+
+ KUNIT_EXPECT_EQ(test, ext4_es_lookup_extent(inode, 50, NULL, &es, NULL),
+ 1);
+ KUNIT_EXPECT_TRUE(test, ext4_es_is_written(&es));
+ KUNIT_EXPECT_EQ(test, ext4_es_pblock(&es), 2100ULL);
+}
+
+/*
+ * Verify ext4_can_extents_be_merged() rules for written/unwritten state,
+ * logical and physical contiguity, and maximum length limits.
+ */
+static void test_can_extents_be_merged(struct kunit *test)
+{
+ struct inode *inode = &k_ctx.k_ei->vfs_inode;
+ struct ext4_extent ex1 = {}, ex2 = {};
+
+ /* Contiguous written extents [10, 15) @ 100 and [15, 20) @ 105 */
+ ex1.ee_block = cpu_to_le32(10);
+ ex1.ee_len = cpu_to_le16(5);
+ ext4_ext_store_pblock(&ex1, 100);
+ ex2.ee_block = cpu_to_le32(15);
+ ex2.ee_len = cpu_to_le16(5);
+ ext4_ext_store_pblock(&ex2, 105);
+ KUNIT_EXPECT_EQ(test, ext4_can_extents_be_merged_test(inode, &ex1, &ex2),
+ 1);
+
+ /* Mismatched written vs unwritten cannot merge */
+ ext4_ext_mark_unwritten(&ex2);
+ KUNIT_EXPECT_EQ(test, ext4_can_extents_be_merged_test(inode, &ex1, &ex2),
+ 0);
+
+ /* Both unwritten and contiguous can merge */
+ ext4_ext_mark_unwritten(&ex1);
+ KUNIT_EXPECT_EQ(test, ext4_can_extents_be_merged_test(inode, &ex1, &ex2),
+ 1);
+
+ /* Non-contiguous physical blocks cannot merge */
+ ext4_ext_mark_initialized(&ex1);
+ ext4_ext_mark_initialized(&ex2);
+ ext4_ext_store_pblock(&ex2, 200);
+ KUNIT_EXPECT_EQ(test, ext4_can_extents_be_merged_test(inode, &ex1, &ex2),
+ 0);
+
+ /* Non-contiguous logical blocks cannot merge */
+ ext4_ext_store_pblock(&ex2, 105);
+ ex2.ee_block = cpu_to_le32(16);
+ KUNIT_EXPECT_EQ(test, ext4_can_extents_be_merged_test(inode, &ex1, &ex2),
+ 0);
+
+ /* Written max length boundary (EXT_INIT_MAX_LEN = 32768) */
+ ex1.ee_block = cpu_to_le32(0);
+ ex1.ee_len = cpu_to_le16(32760);
+ ext4_ext_store_pblock(&ex1, 100);
+ ex2.ee_block = cpu_to_le32(32760);
+ ex2.ee_len = cpu_to_le16(8);
+ ext4_ext_store_pblock(&ex2, 100 + 32760);
+ KUNIT_EXPECT_EQ(test, ext4_can_extents_be_merged_test(inode, &ex1, &ex2),
+ 1);
+ ex2.ee_len = cpu_to_le16(9);
+ KUNIT_EXPECT_EQ(test, ext4_can_extents_be_merged_test(inode, &ex1, &ex2),
+ 0);
+
+ /* Unwritten max length boundary (EXT_UNWRITTEN_MAX_LEN = 32767) */
+ ex1.ee_len = cpu_to_le16(32760);
+ ext4_ext_mark_unwritten(&ex1);
+ ex2.ee_len = cpu_to_le16(7);
+ ext4_ext_mark_unwritten(&ex2);
+ KUNIT_EXPECT_EQ(test, ext4_can_extents_be_merged_test(inode, &ex1, &ex2),
+ 1);
+ ex2.ee_len = cpu_to_le16(8);
+ ext4_ext_mark_unwritten(&ex2);
+ KUNIT_EXPECT_EQ(test, ext4_can_extents_be_merged_test(inode, &ex1, &ex2),
+ 0);
+}
+
+/*
+ * Verify ext4_ext_insert_extent() appending, prepending, 3-way bridge merging,
+ * and EXT4_GET_BLOCKS_SPLIT_NOMERGE suppression.
+ */
+static void test_insert_extent_merge_and_nomerge(struct kunit *test)
+{
+ struct inode *inode = &k_ctx.k_ei->vfs_inode;
+ struct ext4_extent_header *eh = ext_inode_hdr(inode);
+ struct ext4_extent *ex;
+
+ ext_reset_tree(inode);
+
+ /* Insert [10, 15) @ 100 and [20, 25) @ 110 */
+ KUNIT_ASSERT_EQ(test, ext_insert_one(inode, 10, 5, 100, false, 0), 0);
+ KUNIT_ASSERT_EQ(test, ext_insert_one(inode, 20, 5, 110, false, 0), 0);
+ KUNIT_EXPECT_EQ(test, le16_to_cpu(eh->eh_entries), 2);
+
+ /* Append [15, 18) @ 105 -> merges with [10, 15) into [10, 18) */
+ KUNIT_ASSERT_EQ(test, ext_insert_one(inode, 15, 3, 105, false, 0), 0);
+ KUNIT_EXPECT_EQ(test, le16_to_cpu(eh->eh_entries), 2);
+ ex = EXT_FIRST_EXTENT(eh);
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(ex[0].ee_block), 10U);
+ KUNIT_EXPECT_EQ(test, ext4_ext_get_actual_len(&ex[0]), 8);
+
+ /* Insert [18, 20) @ 108 -> bridges [10, 18) and [20, 25) into [10, 25) */
+ KUNIT_ASSERT_EQ(test, ext_insert_one(inode, 18, 2, 108, false, 0), 0);
+ KUNIT_EXPECT_EQ(test, le16_to_cpu(eh->eh_entries), 1);
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(ex[0].ee_block), 10U);
+ KUNIT_EXPECT_EQ(test, ext4_ext_get_actual_len(&ex[0]), 15);
+ KUNIT_EXPECT_EQ(test, ext4_ext_pblock(&ex[0]), 100ULL);
+
+ /* Insert contiguous [25, 30) @ 115 with SPLIT_NOMERGE -> stays separate */
+ KUNIT_ASSERT_EQ(test,
+ ext_insert_one(inode, 25, 5, 115, false,
+ EXT4_GET_BLOCKS_SPLIT_NOMERGE),
+ 0);
+ KUNIT_EXPECT_EQ(test, le16_to_cpu(eh->eh_entries), 2);
+}
+
+/*
+ * Verify tree growth (depth 0 -> 1 via ext4_ext_grow_indepth), parent index
+ * update when inserting a new lowest lblk (ext4_ext_correct_indexes), and
+ * tree collapse back into the inode root (depth 1 -> 0 via
+ * ext4_ext_try_to_merge_up) when merging reduces leaf entries to <= 4.
+ */
+static void test_insert_extent_grow_and_merge_up(struct kunit *test)
+{
+ struct inode *inode = &k_ctx.k_ei->vfs_inode;
+ struct ext4_extent_header *eh = ext_inode_hdr(inode);
+ struct ext4_extent *ex;
+
+ ext_reset_tree(inode);
+
+ /*
+ * Insert 4 non-contiguous extents that fill the root leaf (eh_max = 4):
+ * [10, 12) @ 100, [14, 16) @ 104, [18, 20) @ 108, [22, 24) @ 112.
+ */
+ KUNIT_ASSERT_EQ(test, ext_insert_one(inode, 10, 2, 100, false, 0), 0);
+ KUNIT_ASSERT_EQ(test, ext_insert_one(inode, 14, 2, 104, false, 0), 0);
+ KUNIT_ASSERT_EQ(test, ext_insert_one(inode, 18, 2, 108, false, 0), 0);
+ KUNIT_ASSERT_EQ(test, ext_insert_one(inode, 22, 2, 112, false, 0), 0);
+ KUNIT_EXPECT_EQ(test, ext_depth(inode), 0);
+ KUNIT_EXPECT_EQ(test, le16_to_cpu(eh->eh_entries), 4);
+
+ /*
+ * Insert 5th extent [26, 28) @ 116: triggers ext4_ext_grow_indepth(),
+ * moving the leaf to a newly allocated external block and increasing
+ * tree depth to 1.
+ */
+ KUNIT_ASSERT_EQ(test, ext_insert_one(inode, 26, 2, 116, false, 0), 0);
+ KUNIT_EXPECT_EQ(test, ext_depth(inode), 1);
+ KUNIT_EXPECT_EQ(test, le16_to_cpu(eh->eh_entries), 1);
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(EXT_FIRST_INDEX(eh)->ei_block), 10U);
+ KUNIT_EXPECT_EQ(test, k_ctx.alloc_calls, 1U);
+
+ /*
+ * Prepend [8, 10) @ 98 to [10, 12) @ 100: modifies first extent in the
+ * leaf and triggers ext4_ext_correct_indexes(), updating root ei_block
+ * from 10 to 8.
+ */
+ KUNIT_ASSERT_EQ(test, ext_insert_one(inode, 8, 2, 98, false, 0), 0);
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(EXT_FIRST_INDEX(eh)->ei_block), 8U);
+
+ /*
+ * Bridge two gaps ([12, 14) @ 102 and [16, 18) @ 106): leaf entries
+ * shrink from 5 -> 4 -> 3. Once <= 4 (max_root), ext4_ext_try_to_merge_up()
+ * copies the extents back into the inode root and frees the leaf block!
+ */
+ k_ctx.free_calls = 0;
+ KUNIT_ASSERT_EQ(test, ext_insert_one(inode, 12, 2, 102, false, 0), 0);
+ KUNIT_EXPECT_EQ(test, ext_depth(inode), 0);
+ KUNIT_EXPECT_EQ(test, le16_to_cpu(eh->eh_entries), 4);
+ KUNIT_EXPECT_EQ(test, k_ctx.free_calls, 1U);
+
+ KUNIT_ASSERT_EQ(test, ext_insert_one(inode, 16, 2, 106, false, 0), 0);
+ KUNIT_EXPECT_EQ(test, ext_depth(inode), 0);
+ KUNIT_EXPECT_EQ(test, le16_to_cpu(eh->eh_entries), 3);
+ ex = EXT_FIRST_EXTENT(eh);
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(ex[0].ee_block), 8U);
+ KUNIT_EXPECT_EQ(test, ext4_ext_get_actual_len(&ex[0]), 12);
+}
+
+/*
+ * Verify ext4_ext_remove_space() for middle-of-extent hole punching, full
+ * extent removal with left-scooting, and ext4_ext_truncate() tail removal.
+ */
+static void test_remove_space_tail_and_hole_punch(struct kunit *test)
+{
+ struct inode *inode = &k_ctx.k_ei->vfs_inode;
+ struct ext4_extent_header *eh = ext_inode_hdr(inode);
+ struct ext4_extent *ex;
+ int err;
+
+ ext_reset_tree(inode);
+ KUNIT_ASSERT_EQ(test, ext_insert_one(inode, 10, 10, 100, false, 0), 0);
+ KUNIT_ASSERT_EQ(test, ext_insert_one(inode, 30, 10, 200, false, 0), 0);
+
+ /*
+ * Punch hole [12, 17] inside [10, 20): splits [10, 20) into [10, 12)
+ * and [18, 20), leaving 3 extents total.
+ */
+ down_write(&EXT4_I(inode)->i_data_sem);
+ err = ext4_ext_remove_space(inode, 12, 17);
+ up_write(&EXT4_I(inode)->i_data_sem);
+ KUNIT_ASSERT_EQ(test, err, 0);
+ KUNIT_EXPECT_EQ(test, le16_to_cpu(eh->eh_entries), 3);
+
+ ex = EXT_FIRST_EXTENT(eh);
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(ex[0].ee_block), 10U);
+ KUNIT_EXPECT_EQ(test, ext4_ext_get_actual_len(&ex[0]), 2);
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(ex[1].ee_block), 18U);
+ KUNIT_EXPECT_EQ(test, ext4_ext_get_actual_len(&ex[1]), 2);
+ KUNIT_EXPECT_EQ(test, ext4_ext_pblock(&ex[1]), 108ULL);
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(ex[2].ee_block), 30U);
+ KUNIT_EXPECT_EQ(test, ext4_ext_get_actual_len(&ex[2]), 10);
+
+ /*
+ * Punch out the entire middle extent [18, 19]: scoots [30, 40) into
+ * slot 1 and reduces eh_entries to 2.
+ */
+ down_write(&EXT4_I(inode)->i_data_sem);
+ err = ext4_ext_remove_space(inode, 18, 19);
+ up_write(&EXT4_I(inode)->i_data_sem);
+ KUNIT_ASSERT_EQ(test, err, 0);
+ KUNIT_EXPECT_EQ(test, le16_to_cpu(eh->eh_entries), 2);
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(ex[1].ee_block), 30U);
+
+ /*
+ * Truncate file to 11 blocks (i_size = 11 * 4096): removes [30, 40)
+ * and trims [10, 12) down to [10, 11).
+ */
+ inode->i_size = 11ULL << inode->i_sb->s_blocksize_bits;
+ down_write(&EXT4_I(inode)->i_data_sem);
+ err = ext4_ext_truncate(NULL, inode);
+ up_write(&EXT4_I(inode)->i_data_sem);
+ KUNIT_ASSERT_EQ(test, err, 0);
+ KUNIT_EXPECT_EQ(test, le16_to_cpu(eh->eh_entries), 1);
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(ex[0].ee_block), 10U);
+ KUNIT_EXPECT_EQ(test, ext4_ext_get_actual_len(&ex[0]), 1);
+}
+
+/*
+ * Verify ext4_ext_remove_space() on a depth-1 tree removes all leaf extents,
+ * removes empty index entries via ext4_ext_rm_idx(), frees the leaf metadata
+ * blocks, and resets the root header to eh_depth == 0.
+ */
+static void test_remove_space_multilevel_collapse(struct kunit *test)
+{
+ struct inode *inode = &k_ctx.k_ei->vfs_inode;
+ struct ext4_extent_header *eh = ext_inode_hdr(inode);
+ int err;
+
+ setup_depth1_two_leaf_tree(test, inode);
+ k_ctx.free_calls = 0;
+ k_ctx.total_freed_blocks = 0;
+
+ down_write(&EXT4_I(inode)->i_data_sem);
+ err = ext4_ext_remove_space(inode, 0, EXT_MAX_BLOCKS - 1);
+ up_write(&EXT4_I(inode)->i_data_sem);
+
+ KUNIT_ASSERT_EQ(test, err, 0);
+ KUNIT_EXPECT_EQ(test, ext_depth(inode), 0);
+ KUNIT_EXPECT_EQ(test, le16_to_cpu(eh->eh_entries), 0);
+ KUNIT_EXPECT_EQ(test, le16_to_cpu(eh->eh_max), 4);
+ /* 4 data extents (5 blocks each = 20) + 2 leaf metadata blocks = 22 */
+ KUNIT_EXPECT_EQ(test, k_ctx.total_freed_blocks, 22UL);
+}
+
+/*
+ * Verify bigalloc partial cluster handling in ext4_ext_remove_space():
+ * 1. When a cluster is shared with a remaining extent (state == nofree),
+ * the physical cluster is not freed.
+ * 2. When the entire unaligned extent is removed and has a pending reservation
+ * in i_pending_tree, the cluster is freed with RERESERVE_CLUSTER and
+ * ext4_rereserve_cluster() re-increments i_reserved_data_blocks and removes
+ * the pending reservation.
+ */
+static void test_remove_space_bigalloc_partial_cluster(struct kunit *test)
+{
+ struct inode *inode = &k_ctx.k_ei->vfs_inode;
+ struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb);
+ struct ext4_inode_info *ei = EXT4_I(inode);
+ int err;
+
+ sbi->s_cluster_bits = 2;
+ sbi->s_cluster_ratio = 4;
+
+ /*
+ * Case 1: [0, 2) @ 100 and [2, 4) @ 102 share physical cluster 25
+ * (blocks 100..103). Punching [0, 1] must NOT free cluster 25.
+ */
+ ext_reset_tree(inode);
+ KUNIT_ASSERT_EQ(test,
+ ext_insert_one(inode, 0, 2, 100, false,
+ EXT4_GET_BLOCKS_SPLIT_NOMERGE),
+ 0);
+ KUNIT_ASSERT_EQ(test,
+ ext_insert_one(inode, 2, 2, 102, false,
+ EXT4_GET_BLOCKS_SPLIT_NOMERGE),
+ 0);
+
+ k_ctx.free_calls = 0;
+ k_ctx.total_freed_blocks = 0;
+ down_write(&ei->i_data_sem);
+ err = ext4_ext_remove_space(inode, 0, 1);
+ up_write(&ei->i_data_sem);
+ KUNIT_ASSERT_EQ(test, err, 0);
+ /*
+ * ext4_remove_blocks() is called with NOFREE_FIRST_CLUSTER |
+ * NOFREE_LAST_CLUSTER, and partial cluster 25 is marked nofree.
+ */
+ KUNIT_EXPECT_EQ(test, k_ctx.free_calls, 1U);
+ KUNIT_EXPECT_TRUE(test,
+ (k_ctx.last_free_flags &
+ EXT4_FREE_BLOCKS_NOFREE_FIRST_CLUSTER) != 0);
+
+ /*
+ * Case 2: Single unaligned extent [1, 3) @ 101 in cluster 0 (pclu 25)
+ * with a pending reservation on cluster 0. Truncating from 1 frees
+ * cluster 25 with RERESERVE_CLUSTER and calls ext4_rereserve_cluster().
+ */
+ ext_reset_tree(inode);
+ set_opt(inode->i_sb, DELALLOC);
+ KUNIT_ASSERT_EQ(test, ext_insert_one(inode, 1, 2, 101, true, 0), 0);
+
+ down_write(&ei->i_data_sem);
+ /* Insert delayed extent at lblk 0 in already-allocated cluster 0 */
+ ext4_es_insert_delayed_extent(inode, 0, 1, true, false);
+ up_write(&ei->i_data_sem);
+ KUNIT_ASSERT_TRUE(test, ext4_is_pending(inode, 0));
+
+ ei->i_reserved_data_blocks = 0;
+ k_ctx.free_calls = 0;
+ down_write(&ei->i_data_sem);
+ err = ext4_ext_remove_space(inode, 1, EXT_MAX_BLOCKS - 1);
+ up_write(&ei->i_data_sem);
+
+ KUNIT_ASSERT_EQ(test, err, 0);
+ KUNIT_EXPECT_EQ(test, k_ctx.last_free_pblk, 100ULL);
+ KUNIT_EXPECT_EQ(test, k_ctx.last_free_count, 4UL);
+ KUNIT_EXPECT_TRUE(test,
+ (k_ctx.last_free_flags &
+ EXT4_FREE_BLOCKS_RERESERVE_CLUSTER) != 0);
+ KUNIT_EXPECT_EQ(test, ei->i_reserved_data_blocks, 1U);
+ KUNIT_EXPECT_FALSE(test, ext4_is_pending(inode, 0));
+}
+
+/*
+ * Verify ext4_ext_map_blocks() hole lookup, delayed-extent hole truncation
+ * (ext4_ext_determine_insert_hole), and EXT4_GET_BLOCKS_QUERY_LAST_IN_LEAF.
+ */
+static void test_map_blocks_lookup_hole_and_query_last_in_leaf(struct kunit *test)
+{
+ struct inode *inode = &k_ctx.k_ei->vfs_inode;
+ struct ext4_map_blocks map = {};
+ int ret;
+
+ ext_reset_tree(inode);
+ KUNIT_ASSERT_EQ(test, ext_insert_one(inode, 10, 5, 100, false, 0), 0);
+ KUNIT_ASSERT_EQ(test, ext_insert_one(inode, 20, 5, 200, false, 0), 0);
+
+ /* Hole lookup at [2, 22) returns 0 and trims m_len to [2, 10) (len 8) */
+ map.m_lblk = 2;
+ map.m_len = 20;
+ map.m_flags = 0;
+ down_read(&EXT4_I(inode)->i_data_sem);
+ ret = ext4_ext_map_blocks(NULL, inode, &map, 0);
+ up_read(&EXT4_I(inode)->i_data_sem);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, map.m_pblk, 0ULL);
+ KUNIT_EXPECT_EQ(test, map.m_len, 8U);
+
+ /*
+ * Insert a delayed extent at [5, 7) in the es tree: hole lookup at
+ * [2, 22) must stop at the delayed extent -> [2, 5) (len 3).
+ */
+ down_write(&EXT4_I(inode)->i_data_sem);
+ ext4_es_remove_extent(inode, 0, EXT_MAX_BLOCKS);
+ ext4_es_insert_delayed_extent(inode, 5, 2, false, false);
+ up_write(&EXT4_I(inode)->i_data_sem);
+
+ map.m_lblk = 2;
+ map.m_len = 20;
+ map.m_flags = 0;
+ down_read(&EXT4_I(inode)->i_data_sem);
+ ret = ext4_ext_map_blocks(NULL, inode, &map, EXT4_EX_NOCACHE);
+ up_read(&EXT4_I(inode)->i_data_sem);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, map.m_len, 3U);
+
+ /* QUERY_LAST_IN_LEAF on first extent [10, 15) -> flag NOT set */
+ map.m_lblk = 10;
+ map.m_len = 5;
+ map.m_flags = 0;
+ down_read(&EXT4_I(inode)->i_data_sem);
+ ret = ext4_ext_map_blocks(NULL, inode, &map,
+ EXT4_GET_BLOCKS_QUERY_LAST_IN_LEAF);
+ up_read(&EXT4_I(inode)->i_data_sem);
+ KUNIT_EXPECT_EQ(test, ret, 5);
+ KUNIT_EXPECT_FALSE(test,
+ (map.m_flags & EXT4_MAP_QUERY_LAST_IN_LEAF) != 0);
+
+ /* QUERY_LAST_IN_LEAF on last extent [20, 25) -> flag IS set */
+ map.m_lblk = 20;
+ map.m_len = 5;
+ map.m_flags = 0;
+ down_read(&EXT4_I(inode)->i_data_sem);
+ ret = ext4_ext_map_blocks(NULL, inode, &map,
+ EXT4_GET_BLOCKS_QUERY_LAST_IN_LEAF);
+ up_read(&EXT4_I(inode)->i_data_sem);
+ KUNIT_EXPECT_EQ(test, ret, 5);
+ KUNIT_EXPECT_TRUE(test,
+ (map.m_flags & EXT4_MAP_QUERY_LAST_IN_LEAF) != 0);
+}
+
+/*
+ * Verify ext4_ext_map_blocks() block allocation with overlap clamping
+ * (ext4_ext_check_overlap) and freeing of newly allocated blocks when
+ * ext4_ext_insert_extent() fails with -ENOSPC.
+ */
+static void test_map_blocks_alloc_overlap_and_enospc(struct kunit *test)
+{
+ struct inode *inode = &k_ctx.k_ei->vfs_inode;
+ struct ext4_map_blocks map = {};
+ int ret;
+
+ ext_reset_tree(inode);
+ KUNIT_ASSERT_EQ(test, ext_insert_one(inode, 15, 5, 200, false, 0), 0);
+
+ /*
+ * Request allocation for [10, 20) (len 10): ext4_ext_check_overlap()
+ * clamps the allocation to [10, 15) (len 5) to avoid overlapping [15, 20).
+ */
+ k_ctx.alloc_calls = 0;
+ map.m_lblk = 10;
+ map.m_len = 10;
+ map.m_flags = 0;
+ down_write(&EXT4_I(inode)->i_data_sem);
+ ret = ext4_ext_map_blocks(NULL, inode, &map, EXT4_GET_BLOCKS_CREATE);
+ up_write(&EXT4_I(inode)->i_data_sem);
+
+ KUNIT_EXPECT_EQ(test, ret, 5);
+ KUNIT_EXPECT_EQ(test, map.m_len, 5U);
+ KUNIT_EXPECT_EQ(test, map.m_pblk, EXT_ALLOC_BASE_PBLK);
+ KUNIT_EXPECT_TRUE(test,
+ (map.m_flags & (EXT4_MAP_NEW | EXT4_MAP_MAPPED)) ==
+ (EXT4_MAP_NEW | EXT4_MAP_MAPPED));
+ KUNIT_EXPECT_EQ(test, k_ctx.alloc_calls, 1U);
+ KUNIT_EXPECT_EQ(test, k_ctx.last_alloc_len, 5U);
+
+ /*
+ * Force ext4_ext_insert_extent() to return -ENOSPC and verify that
+ * ext4_ext_map_blocks() frees the newly allocated blocks.
+ */
+ kunit_activate_static_stub(test, ext4_ext_insert_extent,
+ ext4_ext_insert_extent_stub);
+ k_ctx.free_calls = 0;
+ map.m_lblk = 30;
+ map.m_len = 4;
+ map.m_flags = 0;
+ down_write(&EXT4_I(inode)->i_data_sem);
+ ret = ext4_ext_map_blocks(NULL, inode, &map,
+ EXT4_GET_BLOCKS_CREATE |
+ EXT4_GET_BLOCKS_DELALLOC_RESERVE);
+ up_write(&EXT4_I(inode)->i_data_sem);
+
+ KUNIT_EXPECT_EQ(test, ret, -ENOSPC);
+ KUNIT_EXPECT_EQ(test, k_ctx.free_calls, 1U);
+ KUNIT_EXPECT_EQ(test, k_ctx.last_free_pblk, k_ctx.last_alloc_pblk);
+ KUNIT_EXPECT_EQ(test, k_ctx.last_free_count, 4UL);
+ KUNIT_EXPECT_EQ(test, k_ctx.last_free_flags,
+ EXT4_FREE_BLOCKS_NO_QUOT_UPDATE);
+}
+
+/*
+ * Verify ext4_ext_convert_to_initialized() fastpath block transfers to
+ * abutting left (ex - 1) and right (ex + 1) initialized neighbors without
+ * splitting into new extent entries.
+ */
+static void test_convert_to_initialized_abut_neighbors(struct kunit *test)
+{
+ struct inode *inode = &k_ctx.k_ei->vfs_inode;
+ struct ext4_extent_header *eh = ext_inode_hdr(inode);
+ struct ext4_extent *ex;
+ struct ext4_map_blocks map = {};
+ int ret;
+
+ EXT4_SB(inode->i_sb)->s_extent_max_zeroout_kb = 0;
+ ext_reset_tree(inode);
+
+ /*
+ * Setup: [10, 12) @ 100 (written), [12, 16) @ 102 (unwritten),
+ * [16, 18) @ 106 (written).
+ */
+ KUNIT_ASSERT_EQ(test, ext_insert_one(inode, 10, 2, 100, false, 0), 0);
+ KUNIT_ASSERT_EQ(test, ext_insert_one(inode, 12, 4, 102, true, 0), 0);
+ KUNIT_ASSERT_EQ(test, ext_insert_one(inode, 16, 2, 106, false, 0), 0);
+ KUNIT_EXPECT_EQ(test, le16_to_cpu(eh->eh_entries), 3);
+
+ /*
+ * Convert head of unwritten extent [12, 14): transfers 2 blocks to
+ * left neighbor [10, 12) -> [10, 14) written, [14, 16) unwritten.
+ */
+ map.m_lblk = 12;
+ map.m_len = 2;
+ map.m_flags = 0;
+ down_write(&EXT4_I(inode)->i_data_sem);
+ ret = ext4_ext_map_blocks(NULL, inode, &map, EXT4_GET_BLOCKS_CREATE);
+ up_write(&EXT4_I(inode)->i_data_sem);
+ KUNIT_EXPECT_EQ(test, ret, 2);
+ KUNIT_EXPECT_EQ(test, le16_to_cpu(eh->eh_entries), 3);
+
+ ex = EXT_FIRST_EXTENT(eh);
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(ex[0].ee_block), 10U);
+ KUNIT_EXPECT_EQ(test, ext4_ext_get_actual_len(&ex[0]), 4);
+ KUNIT_EXPECT_EQ(test, ext4_ext_is_unwritten(&ex[0]), 0);
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(ex[1].ee_block), 14U);
+ KUNIT_EXPECT_EQ(test, ext4_ext_get_actual_len(&ex[1]), 2);
+ KUNIT_EXPECT_EQ(test, ext4_ext_is_unwritten(&ex[1]), 1);
+
+ /*
+ * Convert tail of unwritten extent [15, 16): transfers 1 block to
+ * right neighbor [16, 18) -> [14, 15) unwritten, [15, 18) written.
+ */
+ map.m_lblk = 15;
+ map.m_len = 1;
+ map.m_flags = 0;
+ down_write(&EXT4_I(inode)->i_data_sem);
+ ret = ext4_ext_map_blocks(NULL, inode, &map, EXT4_GET_BLOCKS_CREATE);
+ up_write(&EXT4_I(inode)->i_data_sem);
+ KUNIT_EXPECT_EQ(test, ret, 1);
+ KUNIT_EXPECT_EQ(test, le16_to_cpu(eh->eh_entries), 3);
+
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(ex[1].ee_block), 14U);
+ KUNIT_EXPECT_EQ(test, ext4_ext_get_actual_len(&ex[1]), 1);
+ KUNIT_EXPECT_EQ(test, ext4_ext_is_unwritten(&ex[1]), 1);
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(ex[2].ee_block), 15U);
+ KUNIT_EXPECT_EQ(test, ext4_ext_get_actual_len(&ex[2]), 3);
+ KUNIT_EXPECT_EQ(test, ext4_ext_pblock(&ex[2]), 105ULL);
+ KUNIT_EXPECT_EQ(test, ext4_ext_is_unwritten(&ex[2]), 0);
+}
+
+/*
+ * Verify bigalloc get_implied_cluster_alloc() in ext4_ext_map_blocks() when
+ * an existing extent to the left or right already maps the same logical
+ * cluster, avoiding a duplicate physical cluster allocation.
+ */
+static void test_map_blocks_bigalloc_implied_cluster(struct kunit *test)
+{
+ struct inode *inode = &k_ctx.k_ei->vfs_inode;
+ struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb);
+ struct ext4_map_blocks map = {};
+ int ret;
+
+ sbi->s_cluster_bits = 2;
+ sbi->s_cluster_ratio = 4;
+
+ /*
+ * Case 1: Left extent [8, 9) @ 100 in logical cluster 2 (lblks 8..11).
+ * Allocating [10, 12) reuses physical cluster 25 (pblks 102..103)
+ * without calling ext4_mb_new_blocks().
+ */
+ ext_reset_tree(inode);
+ KUNIT_ASSERT_EQ(test, ext_insert_one(inode, 8, 1, 100, false, 0), 0);
+
+ k_ctx.alloc_calls = 0;
+ map.m_lblk = 10;
+ map.m_len = 2;
+ map.m_flags = 0;
+ down_write(&EXT4_I(inode)->i_data_sem);
+ ret = ext4_ext_map_blocks(NULL, inode, &map, EXT4_GET_BLOCKS_CREATE);
+ up_write(&EXT4_I(inode)->i_data_sem);
+
+ KUNIT_EXPECT_EQ(test, ret, 2);
+ KUNIT_EXPECT_EQ(test, map.m_pblk, 102ULL);
+ KUNIT_EXPECT_EQ(test, k_ctx.alloc_calls, 0U);
+
+ /*
+ * Case 2: Right extent [10, 12) @ 102 in logical cluster 2 (lblks 8..11).
+ * Allocating [8, 10) searches right, finds [10, 12), and reuses
+ * physical cluster 25 (pblks 100..101) without calling ext4_mb_new_blocks().
+ */
+ ext_reset_tree(inode);
+ KUNIT_ASSERT_EQ(test, ext_insert_one(inode, 10, 2, 102, false, 0), 0);
+
+ k_ctx.alloc_calls = 0;
+ map.m_lblk = 8;
+ map.m_len = 2;
+ map.m_flags = 0;
+ down_write(&EXT4_I(inode)->i_data_sem);
+ ret = ext4_ext_map_blocks(NULL, inode, &map, EXT4_GET_BLOCKS_CREATE);
+ up_write(&EXT4_I(inode)->i_data_sem);
+
+ KUNIT_EXPECT_EQ(test, ret, 2);
+ KUNIT_EXPECT_EQ(test, map.m_pblk, 100ULL);
+ KUNIT_EXPECT_EQ(test, k_ctx.alloc_calls, 0U);
+}
+
+/*
+ * Verify ext4_ext_shift_extents() for SHIFT_LEFT (collapse range) with
+ * neighbor merging, SHIFT_RIGHT (insert range), and -EINVAL boundary checks.
+ */
+static void test_ext_shift_extents(struct kunit *test)
+{
+ struct inode *inode = &k_ctx.k_ei->vfs_inode;
+ struct ext4_extent_header *eh = ext_inode_hdr(inode);
+ struct ext4_extent *ex;
+ int err;
+
+ ext_reset_tree(inode);
+ /* [10, 15) @ 100 and [20, 25) @ 105 (physically contiguous!) */
+ KUNIT_ASSERT_EQ(test, ext_insert_one(inode, 10, 5, 100, false, 0), 0);
+ KUNIT_ASSERT_EQ(test, ext_insert_one(inode, 20, 5, 105, false, 0), 0);
+
+ /* Shifting [20, 25) left by 6 overlaps [10, 15) -> -EINVAL */
+ down_write(&EXT4_I(inode)->i_data_sem);
+ err = ext4_ext_shift_extents_test(inode, NULL, 20, 6, SHIFT_LEFT);
+ up_write(&EXT4_I(inode)->i_data_sem);
+ KUNIT_EXPECT_EQ(test, err, -EINVAL);
+
+ /* Shifting [20, 25) left by 5 merges with [10, 15) into [10, 20) @ 100 */
+ down_write(&EXT4_I(inode)->i_data_sem);
+ err = ext4_ext_shift_extents_test(inode, NULL, 20, 5, SHIFT_LEFT);
+ up_write(&EXT4_I(inode)->i_data_sem);
+ KUNIT_EXPECT_EQ(test, err, 0);
+ KUNIT_EXPECT_EQ(test, le16_to_cpu(eh->eh_entries), 1);
+ ex = EXT_FIRST_EXTENT(eh);
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(ex[0].ee_block), 10U);
+ KUNIT_EXPECT_EQ(test, ext4_ext_get_actual_len(&ex[0]), 10);
+
+ /* Shifting [10, 20) right by 4 moves it to [14, 24) @ 100 */
+ down_write(&EXT4_I(inode)->i_data_sem);
+ err = ext4_ext_shift_extents_test(inode, NULL, 10, 4, SHIFT_RIGHT);
+ up_write(&EXT4_I(inode)->i_data_sem);
+ KUNIT_EXPECT_EQ(test, err, 0);
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(ex[0].ee_block), 14U);
+ KUNIT_EXPECT_EQ(test, ext4_ext_get_actual_len(&ex[0]), 10);
+
+ /* Shifting right past EXT_MAX_BLOCKS -> -EINVAL */
+ down_write(&EXT4_I(inode)->i_data_sem);
+ err = ext4_ext_shift_extents_test(inode, NULL, 14, EXT_MAX_BLOCKS,
+ SHIFT_RIGHT);
+ up_write(&EXT4_I(inode)->i_data_sem);
+ KUNIT_EXPECT_EQ(test, err, -EINVAL);
+}
+
+/*
+ * Verify ext4_swap_extents() splits unaligned boundaries on both inodes,
+ * swaps physical block mappings for the requested subrange, and marks
+ * the donor inode's swapped extent unwritten when requested.
+ */
+static void test_swap_extents(struct kunit *test)
+{
+ struct inode *inode1 = &k_ctx.k_ei->vfs_inode;
+ struct inode *inode2;
+ struct ext4_extent_header *eh1, *eh2;
+ struct ext4_extent *ex1, *ex2;
+ int replaced, err = 0;
+
+ k_ctx.k_ei2 = kzalloc_obj(struct ext4_inode_info);
+ KUNIT_ASSERT_NOT_NULL(test, k_ctx.k_ei2);
+ ext_init_mock_inode(k_ctx.k_ei2, inode1->i_sb, 13);
+ inode2 = &k_ctx.k_ei2->vfs_inode;
+
+ ext_reset_tree(inode1);
+ ext_reset_tree(inode2);
+
+ /* inode1: [10, 20) @ 100; inode2: [10, 20) @ 200 */
+ KUNIT_ASSERT_EQ(test, ext_insert_one(inode1, 10, 10, 100, false, 0), 0);
+ KUNIT_ASSERT_EQ(test, ext_insert_one(inode2, 10, 10, 200, false, 0), 0);
+
+ inode_lock(inode1);
+ inode_lock(inode2);
+ down_write(&EXT4_I(inode1)->i_data_sem);
+ down_write(&EXT4_I(inode2)->i_data_sem);
+
+ replaced = ext4_swap_extents(NULL, inode1, inode2, 12, 12, 5, 1, &err);
+
+ up_write(&EXT4_I(inode2)->i_data_sem);
+ up_write(&EXT4_I(inode1)->i_data_sem);
+ inode_unlock(inode2);
+ inode_unlock(inode1);
+
+ KUNIT_EXPECT_EQ(test, err, 0);
+ KUNIT_EXPECT_EQ(test, replaced, 5);
+
+ eh1 = ext_inode_hdr(inode1);
+ eh2 = ext_inode_hdr(inode2);
+ KUNIT_ASSERT_EQ(test, le16_to_cpu(eh1->eh_entries), 3);
+ KUNIT_ASSERT_EQ(test, le16_to_cpu(eh2->eh_entries), 3);
+
+ ex1 = EXT_FIRST_EXTENT(eh1);
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(ex1[0].ee_block), 10U);
+ KUNIT_EXPECT_EQ(test, ext4_ext_pblock(&ex1[0]), 100ULL);
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(ex1[1].ee_block), 12U);
+ KUNIT_EXPECT_EQ(test, ext4_ext_get_actual_len(&ex1[1]), 5);
+ KUNIT_EXPECT_EQ(test, ext4_ext_pblock(&ex1[1]), 202ULL);
+ KUNIT_EXPECT_EQ(test, ext4_ext_is_unwritten(&ex1[1]), 0);
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(ex1[2].ee_block), 17U);
+ KUNIT_EXPECT_EQ(test, ext4_ext_pblock(&ex1[2]), 107ULL);
+
+ ex2 = EXT_FIRST_EXTENT(eh2);
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(ex2[0].ee_block), 10U);
+ KUNIT_EXPECT_EQ(test, ext4_ext_pblock(&ex2[0]), 200ULL);
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(ex2[1].ee_block), 12U);
+ KUNIT_EXPECT_EQ(test, ext4_ext_get_actual_len(&ex2[1]), 5);
+ KUNIT_EXPECT_EQ(test, ext4_ext_pblock(&ex2[1]), 102ULL);
+ KUNIT_EXPECT_EQ(test, ext4_ext_is_unwritten(&ex2[1]), 1);
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(ex2[2].ee_block), 17U);
+ KUNIT_EXPECT_EQ(test, ext4_ext_pblock(&ex2[2]), 207ULL);
+}
+
+/*
+ * Verify ext4_clu_mapped() across inline-data inodes, empty trees, clusters
+ * mapped at the start or end of a cluster, and unmapped clusters.
+ */
+static void test_clu_mapped(struct kunit *test)
+{
+ struct inode *inode = &k_ctx.k_ei->vfs_inode;
+ struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb);
+
+ sbi->s_cluster_bits = 2;
+ sbi->s_cluster_ratio = 4;
+
+ ext_reset_tree(inode);
+ /* Empty tree -> unmapped */
+ KUNIT_EXPECT_EQ(test, ext4_clu_mapped(inode, 0), 0);
+
+ /*
+ * Insert [0, 2) @ 100 (in lclu 0 = lblks 0..3) and
+ * [6, 8) @ 106 (in lclu 1 = lblks 4..7).
+ */
+ KUNIT_ASSERT_EQ(test, ext_insert_one(inode, 0, 2, 100, false, 0), 0);
+ KUNIT_ASSERT_EQ(test, ext_insert_one(inode, 6, 2, 106, false, 0), 0);
+
+ /* Inline-data state returns 0 immediately */
+ ext4_set_inode_state(inode, EXT4_STATE_MAY_INLINE_DATA);
+ KUNIT_EXPECT_EQ(test, ext4_clu_mapped(inode, 0), 0);
+ ext4_clear_inode_state(inode, EXT4_STATE_MAY_INLINE_DATA);
+
+ /* lclu 0 (lblk 0) is mapped directly by [0, 2) */
+ KUNIT_EXPECT_EQ(test, ext4_clu_mapped(inode, 0), 1);
+ /* lclu 1 (lblk 4) is mapped by next allocated extent [6, 8) */
+ KUNIT_EXPECT_EQ(test, ext4_clu_mapped(inode, 1), 1);
+ /* lclu 2 (lblk 8..11) is unmapped */
+ KUNIT_EXPECT_EQ(test, ext4_clu_mapped(inode, 2), 0);
+}
+
+/*
+ * Verify ext4_ext_index_trans_blocks() and
+ * ext4_ext_calc_credits_for_single_extent() credit calculations.
+ */
+static void test_ext_credits_calculation(struct kunit *test)
+{
+ struct inode *inode = &k_ctx.k_ei->vfs_inode;
+ struct ext4_extent_header *eh = ext_inode_hdr(inode);
+ struct ext4_ext_path *path;
+ int credits_with_space, credits_full;
+
+ /* 1 extent vs multiple extents */
+ KUNIT_EXPECT_EQ(test, ext4_ext_index_trans_blocks(inode, 1),
+ EXT4_MAX_EXTENT_DEPTH * 2 + 1);
+ KUNIT_EXPECT_EQ(test, ext4_ext_index_trans_blocks(inode, 2),
+ EXT4_MAX_EXTENT_DEPTH * 3 + 2);
+
+ /* Inline data inode needs only 1 index block credit */
+ ext4_set_inode_flag(inode, EXT4_INODE_INLINE_DATA);
+ EXT4_I(inode)->i_inline_off = 1;
+ KUNIT_EXPECT_EQ(test, ext4_ext_index_trans_blocks(inode, 2), 1);
+ EXT4_I(inode)->i_inline_off = 0;
+ ext4_clear_inode_flag(inode, EXT4_INODE_INLINE_DATA);
+
+ ext_reset_tree(inode);
+ KUNIT_ASSERT_EQ(test, ext_insert_one(inode, 10, 2, 100, false, 0), 0);
+
+ path = ext4_find_extent(inode, 10, NULL, EXT4_EX_NOCACHE);
+ KUNIT_ASSERT_FALSE(test, IS_ERR_OR_NULL(path));
+ /* Leaf has free space (eh_entries = 1 < eh_max = 4) */
+ credits_with_space =
+ ext4_ext_calc_credits_for_single_extent(inode, 1, path);
+
+ /* Mark leaf full (eh_entries == eh_max) */
+ eh->eh_entries = eh->eh_max;
+ credits_full = ext4_ext_calc_credits_for_single_extent(inode, 1, path);
+ eh->eh_entries = cpu_to_le16(1);
+ ext4_free_ext_path(path);
+
+ KUNIT_EXPECT_LT(test, credits_with_space, credits_full);
+ KUNIT_EXPECT_EQ(test, credits_full,
+ ext4_ext_calc_credits_for_single_extent(inode, 1, NULL));
+}
+
+/*
+ * Verify fast-commit replay extent helpers:
+ * - ext4_ext_replay_update_ex() splits and updates extent state/pblk
+ * - ext4_ext_replay_shrink_inode() merges adjacent compatible extents
+ * - ext4_ext_replay_set_iblocks() counts data and extent tree blocks and
+ * updates inode->i_blocks.
+ */
+static void test_ext_fast_commit_replay(struct kunit *test)
+{
+ struct inode *inode = &k_ctx.k_ei->vfs_inode;
+ struct ext4_extent_header *eh = ext_inode_hdr(inode);
+ struct ext4_extent *ex;
+ int err;
- return;
+ ext_reset_tree(inode);
+ /* Start with unwritten extent [10, 20) @ 100 */
+ KUNIT_ASSERT_EQ(test, ext_insert_one(inode, 10, 10, 100, true, 0), 0);
+
+ /* Replay update converting middle [12, 16) to written @ 102 */
+ err = ext4_ext_replay_update_ex(inode, 12, 4, 0, 102);
+ KUNIT_ASSERT_EQ(test, err, 0);
+ KUNIT_EXPECT_EQ(test, le16_to_cpu(eh->eh_entries), 3);
+
+ /* Replay update converting [10, 12) @ 100 and [16, 20) @ 106 to written */
+ KUNIT_ASSERT_EQ(test, ext4_ext_replay_update_ex(inode, 10, 2, 0, 100),
+ 0);
+ KUNIT_ASSERT_EQ(test, ext4_ext_replay_update_ex(inode, 16, 4, 0, 106),
+ 0);
+ KUNIT_EXPECT_EQ(test, le16_to_cpu(eh->eh_entries), 3);
+
+ /* Shrink inode merges the 3 contiguous written extents into [10, 20) */
+ ext4_ext_replay_shrink_inode(inode, 25);
+ KUNIT_EXPECT_EQ(test, le16_to_cpu(eh->eh_entries), 1);
+ ex = EXT_FIRST_EXTENT(eh);
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(ex[0].ee_block), 10U);
+ KUNIT_EXPECT_EQ(test, ext4_ext_get_actual_len(&ex[0]), 10);
+ KUNIT_EXPECT_EQ(test, ext4_ext_is_unwritten(&ex[0]), 0);
+
+ /* Recalculate i_blocks for the 10-block extent (10 * 8 = 80 sectors) */
+ inode->i_size = 30ULL << inode->i_sb->s_blocksize_bits;
+ inode->i_blocks = 0;
+ err = ext4_ext_replay_set_iblocks(inode);
+ KUNIT_EXPECT_EQ(test, err, 0);
+ KUNIT_EXPECT_EQ(test, inode->i_blocks,
+ 10ULL << (inode->i_sb->s_blocksize_bits - 9));
}
static const struct kunit_ext_test_param test_split_convert_params[] = {
@@ -1049,6 +2774,31 @@ static struct kunit_case extents_test_cases[] = {
test_convert_initialized_param_init, NULL),
KUNIT_CASE_PARAM_WITH_INIT(test_split_convert, kunit_array_gen_params,
test_handle_unwritten_init, NULL),
+ KUNIT_CASE(test_ext_pblock_48bit),
+ KUNIT_CASE(test_ext_unwritten_len_encoding),
+ KUNIT_CASE(test_ext_tree_init_and_check_inode),
+ KUNIT_CASE_PARAM_WITH_INIT(test_ext_check_corruptions,
+ kunit_array_gen_params,
+ ext_check_corrupt_param_init, NULL),
+ KUNIT_CASE(test_ext_block_csum_verify),
+ KUNIT_CASE(test_find_extent_root_binsearch),
+ KUNIT_CASE(test_find_extent_multilevel_and_navigation),
+ KUNIT_CASE(test_ext_precache),
+ KUNIT_CASE(test_can_extents_be_merged),
+ KUNIT_CASE(test_insert_extent_merge_and_nomerge),
+ KUNIT_CASE(test_insert_extent_grow_and_merge_up),
+ KUNIT_CASE(test_remove_space_tail_and_hole_punch),
+ KUNIT_CASE(test_remove_space_multilevel_collapse),
+ KUNIT_CASE(test_remove_space_bigalloc_partial_cluster),
+ KUNIT_CASE(test_map_blocks_lookup_hole_and_query_last_in_leaf),
+ KUNIT_CASE(test_map_blocks_alloc_overlap_and_enospc),
+ KUNIT_CASE(test_convert_to_initialized_abut_neighbors),
+ KUNIT_CASE(test_map_blocks_bigalloc_implied_cluster),
+ KUNIT_CASE(test_ext_shift_extents),
+ KUNIT_CASE(test_swap_extents),
+ KUNIT_CASE(test_clu_mapped),
+ KUNIT_CASE(test_ext_credits_calculation),
+ KUNIT_CASE(test_ext_fast_commit_replay),
{}
};
diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
index 6c018bcc0118..efbee7aaa95d 100644
--- a/fs/ext4/extents.c
+++ b/fs/ext4/extents.c
@@ -547,6 +547,13 @@ static void ext4_cache_extents(struct inode *inode,
}
}
+struct buffer_head *ext4_ext_getblk(struct inode *inode, ext4_fsblk_t pblk,
+ gfp_t gfp_flags)
+{
+ KUNIT_STATIC_STUB_REDIRECT(ext4_ext_getblk, inode, pblk, gfp_flags);
+ return sb_getblk_gfp(inode->i_sb, pblk, gfp_flags);
+}
+
static struct buffer_head *
__read_extent_tree_block(const char *function, unsigned int line,
struct inode *inode, struct ext4_extent_idx *idx,
@@ -561,7 +568,7 @@ __read_extent_tree_block(const char *function, unsigned int line,
gfp_flags |= __GFP_NOFAIL;
pblk = ext4_idx_pblock(idx);
- bh = sb_getblk_gfp(inode->i_sb, pblk, gfp_flags);
+ bh = ext4_ext_getblk(inode, pblk, gfp_flags);
if (unlikely(!bh))
return ERR_PTR(-ENOMEM);
@@ -1124,7 +1131,7 @@ static int ext4_ext_split(handle_t *handle, struct inode *inode,
err = -EFSCORRUPTED;
goto cleanup;
}
- bh = sb_getblk_gfp(inode->i_sb, newblock, __GFP_MOVABLE | GFP_NOFS);
+ bh = ext4_ext_getblk(inode, newblock, __GFP_MOVABLE | GFP_NOFS);
if (unlikely(!bh)) {
err = -ENOMEM;
goto cleanup;
@@ -1203,7 +1210,7 @@ static int ext4_ext_split(handle_t *handle, struct inode *inode,
while (k--) {
oldblock = newblock;
newblock = ablocks[--a];
- bh = sb_getblk(inode->i_sb, newblock);
+ bh = ext4_ext_getblk(inode, newblock, __GFP_MOVABLE | GFP_NOFS);
if (unlikely(!bh)) {
err = -ENOMEM;
goto cleanup;
@@ -1332,7 +1339,7 @@ static int ext4_ext_grow_indepth(handle_t *handle, struct inode *inode,
if (newblock == 0)
return err;
- bh = sb_getblk_gfp(inode->i_sb, newblock, __GFP_MOVABLE | GFP_NOFS);
+ bh = ext4_ext_getblk(inode, newblock, __GFP_MOVABLE | GFP_NOFS);
if (unlikely(!bh))
return -ENOMEM;
lock_buffer(bh);
@@ -6405,11 +6412,79 @@ struct ext4_ext_path *ext4_split_convert_extents_test(handle_t *handle,
}
EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_split_convert_extents_test);
+int ext4_ext_check_test(struct inode *inode, struct ext4_extent_header *eh,
+ int depth, ext4_fsblk_t pblk, ext4_lblk_t lblk)
+{
+ return __ext4_ext_check(__func__, __LINE__, inode, eh, depth, pblk,
+ lblk);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_ext_check_test);
+
+void ext4_extent_block_csum_set_test(struct inode *inode,
+ struct ext4_extent_header *eh)
+{
+ ext4_extent_block_csum_set(inode, eh);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_extent_block_csum_set_test);
+
+int ext4_can_extents_be_merged_test(struct inode *inode,
+ struct ext4_extent *ex1,
+ struct ext4_extent *ex2)
+{
+ return ext4_can_extents_be_merged(inode, ex1, ex2);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_can_extents_be_merged_test);
+
+int ext4_ext_search_left_test(struct inode *inode, struct ext4_ext_path *path,
+ ext4_lblk_t *logical, ext4_fsblk_t *phys)
+{
+ return ext4_ext_search_left(inode, path, logical, phys);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_ext_search_left_test);
+
+int ext4_ext_search_right_test(struct inode *inode, struct ext4_ext_path *path,
+ ext4_lblk_t *logical, ext4_fsblk_t *phys,
+ struct ext4_extent *ret_ex, int flags)
+{
+ return ext4_ext_search_right(inode, path, logical, phys, ret_ex, flags);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_ext_search_right_test);
+
+ext4_lblk_t ext4_ext_next_leaf_block_test(struct ext4_ext_path *path)
+{
+ return ext4_ext_next_leaf_block(path);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_ext_next_leaf_block_test);
+
+int ext4_ext_shift_extents_test(struct inode *inode, handle_t *handle,
+ ext4_lblk_t start, ext4_lblk_t shift,
+ enum SHIFT_DIRECTION direction)
+{
+ return ext4_ext_shift_extents(inode, handle, start, shift, direction);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_ext_shift_extents_test);
+
EXPORT_SYMBOL_FOR_EXT4_TEST(__ext4_ext_dirty);
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_ext_getblk);
EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_ext_zeroout);
EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_map_create_blocks);
EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_ext_insert_extent);
EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_find_extent);
EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_issue_zeroout);
EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_map_query_blocks);
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_free_ext_path);
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_ext_tree_init);
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_ext_check_inode);
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_ext_precache);
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_ext_next_allocated_block);
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_ext_calc_credits_for_single_extent);
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_ext_index_trans_blocks);
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_ext_remove_space);
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_ext_truncate);
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_ext_map_blocks);
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_swap_extents);
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_clu_mapped);
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_ext_replay_update_ex);
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_ext_replay_shrink_inode);
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_ext_replay_set_iblocks);
#endif
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index 966a760f64e6..289f7fa35090 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -6699,6 +6699,9 @@ int __ext4_mark_inode_dirty(handle_t *handle, struct inode *inode,
struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb);
int err;
+ KUNIT_STATIC_STUB_REDIRECT(__ext4_mark_inode_dirty, handle, inode,
+ func, line);
+
might_sleep();
trace_ext4_mark_inode_dirty(inode, _RET_IP_);
err = ext4_reserve_inode_write(handle, inode, &iloc);
@@ -6982,4 +6985,6 @@ vm_fault_t ext4_page_mkwrite(struct vm_fault *vmf)
#if IS_ENABLED(CONFIG_EXT4_KUNIT_TESTS)
EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_da_update_reserve_space);
EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_da_release_space);
+EXPORT_SYMBOL_FOR_EXT4_TEST(__ext4_mark_inode_dirty);
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_map_blocks);
#endif
diff --git a/fs/ext4/mballoc.c b/fs/ext4/mballoc.c
index 06171a11db12..0f73fb826dbf 100644
--- a/fs/ext4/mballoc.c
+++ b/fs/ext4/mballoc.c
@@ -6239,6 +6239,8 @@ ext4_fsblk_t ext4_mb_new_blocks(handle_t *handle,
int retries = 0;
u64 seq;
+ KUNIT_STATIC_STUB_REDIRECT(ext4_mb_new_blocks, handle, ar, errp);
+
might_sleep();
sb = ar->inode->i_sb;
sbi = EXT4_SB(sb);
@@ -6697,6 +6699,9 @@ void ext4_free_blocks(handle_t *handle, struct inode *inode,
unsigned int overflow;
struct ext4_sb_info *sbi;
+ KUNIT_STATIC_STUB_REDIRECT(ext4_free_blocks, handle, inode, bh, block,
+ count, flags);
+
sbi = EXT4_SB(sb);
if (bh) {
@@ -7281,4 +7286,6 @@ EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_read_block_bitmap_nowait);
EXPORT_SYMBOL_FOR_EXT4_TEST(mb_set_bits);
EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_fc_init_inode);
EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_mb_mark_context);
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_mb_new_blocks);
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_free_blocks);
#endif
--
2.56.0.542.g3ef89b7020-goog
next prev parent reply other threads:[~2026-11-12 4:07 UTC|newest]
Thread overview: 55+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-12 4:06 [PATCH 00/27] ext4: add lots of KUnit tests! Theodore Ts'o
2026-11-12 4:06 ` [PATCH 01/27] ext4: add KUinit tests for fs/ext4/extents_status.c Theodore Ts'o
2026-11-12 4:18 ` sashiko-bot
2026-11-12 4:06 ` Theodore Ts'o [this message]
2026-11-12 4:28 ` [PATCH 02/27] ext4: add KUnit tests for fs/ext4/extents.c sashiko-bot
2026-11-12 4:06 ` [PATCH 03/27] ext4: add KUnit tests for ext4_ext_split() leaf and index splitting Theodore Ts'o
2026-11-12 4:15 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 04/27] ext4: add KUnit tests for fs/ext4/xattr.c Theodore Ts'o
2026-11-12 4:26 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 05/27] ext4: add KUnit tests for fs/ext4/namei.c Theodore Ts'o
2026-11-12 4:21 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 06/27] ext4: expand KUnit tests for fs/ext4/mballoc.c Theodore Ts'o
2026-11-12 4:26 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 07/27] ext4: add KUnit tests for fs/ext4/inline.c Theodore Ts'o
2026-11-12 4:29 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 08/27] ext4: zero s_fc_replay_state in ext4_fc_replay_cleanup() Theodore Ts'o
2026-11-12 4:16 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 09/27] ext4: add KUnit tests for fs/ext4/fast_commit.c Theodore Ts'o
2026-11-12 4:28 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 10/27] ext4: expand KUnit tests for fs/ext4/inode.c Theodore Ts'o
2026-11-12 4:36 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 11/27] ext4: add KUnit tests for fs/ext4/ialloc.c Theodore Ts'o
2026-11-12 4:25 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 12/27] ext4: add KUnit tests for fs/ext4/balloc.c Theodore Ts'o
2026-11-12 4:16 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 13/27] ext4: add KUnit tests for fs/ext4/orphan.c Theodore Ts'o
2026-11-12 4:18 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 14/27] ext4: add KUnit tests for fs/ext4/block_validity.c Theodore Ts'o
2026-11-12 4:21 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 15/27] ext4: check multi-block overlap on left traversal in add_system_zone() Theodore Ts'o
2026-11-12 4:10 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 16/27] ext4: enable full feature coverage in fs/ext4/.kunitconfig Theodore Ts'o
2026-11-12 4:24 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 17/27] ext4: add KUnit tests for fs/ext4/super.c Theodore Ts'o
2026-11-12 4:27 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 18/27] ext4: add KUnit tests for fs/ext4/sysfs.c Theodore Ts'o
2026-11-12 4:32 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 19/27] ext4: add KUnit tests for fs/ext4/ioctl.c Theodore Ts'o
2026-11-12 4:35 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 20/27] ext4: add KUnit tests for fs/ext4/resize.c Theodore Ts'o
2026-11-12 4:34 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 21/27] ext4: add KUnit tests for fs/ext4/migrate.c Theodore Ts'o
2026-11-12 4:37 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 22/27] ext4: add KUnit tests for fs/ext4/indirect.c Theodore Ts'o
2026-11-12 4:46 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 23/27] ext4: expand KUnit tests in fs/ext4/extents-test.c for file range operations Theodore Ts'o
2026-11-12 4:34 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 24/27] ext4: add KUnit tests for fs/ext4/fsmap.c Theodore Ts'o
2026-11-12 4:30 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 25/27] ext4: add KUnit tests for fs/ext4/move_extent.c Theodore Ts'o
2026-11-12 4:36 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 26/27] ext4: add KUnit tests for fs/ext4/mmp.c Theodore Ts'o
2026-11-12 4:29 ` sashiko-bot
2026-11-12 4:06 ` [PATCH 27/27] ext4: add KUnit tests for fs/ext4/dir.c Theodore Ts'o
2026-11-12 4:41 ` sashiko-bot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261112040638.1576836-3-tytso@mit.edu \
--to=tytso@mit.edu \
--cc=linux-ext4@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox