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 25/27] ext4: add KUnit tests for fs/ext4/move_extent.c
Date: Wed, 11 Nov 2026 23:06:36 -0500 [thread overview]
Message-ID: <20261112040638.1576836-26-tytso@mit.edu> (raw)
In-Reply-To: <20261112040638.1576836-1-tytso@mit.edu>
Add a KUnit test suite (ext4_move_extent_test) covering online extent
swapping and validation helpers in fs/ext4/move_extent.c:
- Ordered i_data_sem lock acquisition/release in
ext4_double_down_write_data_sem() and ext4_double_up_write_data_sem()
- File, feature, and mode checks in mext_check_validity() (same inode,
cross-sb, non-regular, bigalloc, DAX, data journaling, encrypted,
non-extent, SUID/SGID, immutable/append, swapfile, quota, zero size)
- Sub-page alignment, EXT_MAX_BLOCKS bounds, and EOF clamping in
mext_check_adjust_range()
- End-to-end ext4_move_extents() across origin/donor holes, unwritten
extent moves, written MEXT_COPY_DATA with extent splitting and page
cache preservation, unwritten-origin zeroing, and -EBUSY release
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
---
fs/ext4/Makefile | 3 +-
fs/ext4/ext4.h | 8 +
fs/ext4/move_extent-test.c | 997 +++++++++++++++++++++++++++++++++++++
fs/ext4/move_extent.c | 21 +-
4 files changed, 1026 insertions(+), 3 deletions(-)
create mode 100644 fs/ext4/move_extent-test.c
diff --git a/fs/ext4/Makefile b/fs/ext4/Makefile
index e1964407be24..5cf735df7656 100644
--- a/fs/ext4/Makefile
+++ b/fs/ext4/Makefile
@@ -23,7 +23,8 @@ ext4-test-objs += inode-test.o mballoc-test.o \
block_validity-test.o super-test.o \
sysfs-test.o ioctl-test.o \
resize-test.o migrate-test.o \
- indirect-test.o fsmap-test.o
+ indirect-test.o fsmap-test.o \
+ move_extent-test.o
obj-$(CONFIG_EXT4_KUNIT_TESTS) += ext4-test.o
ext4-$(CONFIG_FS_VERITY) += verity.o
ext4-$(CONFIG_FS_ENCRYPTION) += crypto.o
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index 4db096244a81..8bc7269af580 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -4180,6 +4180,14 @@ extern void ext4_double_up_write_data_sem(struct inode *orig_inode,
extern int ext4_move_extents(struct file *o_filp, struct file *d_filp,
__u64 start_orig, __u64 start_donor,
__u64 len, __u64 *moved_len);
+#if IS_ENABLED(CONFIG_EXT4_KUNIT_TESTS)
+extern int ext4_mext_check_validity_test(struct inode *orig_inode,
+ struct inode *donor_inode);
+extern int ext4_mext_check_adjust_range_test(struct inode *orig_inode,
+ struct inode *donor_inode,
+ __u64 orig_start,
+ __u64 donor_start, __u64 *len);
+#endif
/* page-io.c */
extern int __init ext4_init_pageio(void);
diff --git a/fs/ext4/move_extent-test.c b/fs/ext4/move_extent-test.c
new file mode 100644
index 000000000000..e29a392eeebe
--- /dev/null
+++ b/fs/ext4/move_extent-test.c
@@ -0,0 +1,997 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * KUnit tests for fs/ext4/move_extent.c
+ */
+
+#include <kunit/static_stub.h>
+#include <kunit/test.h>
+#include <linux/blkdev.h>
+#include <linux/buffer_head.h>
+#include <linux/dcache.h>
+#include <linux/fs.h>
+#include <linux/fs_context.h>
+#include <linux/fscrypt.h>
+#include <linux/iversion.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/pagemap.h>
+#include <linux/part_stat.h>
+#include <linux/percpu.h>
+#include <linux/percpu_counter.h>
+#include <linux/quotaops.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/workqueue.h>
+
+#include "ext4.h"
+#include "ext4_extents.h"
+#include "ext4_jbd2.h"
+
+#define MEXT_TEST_BLK_SIZE 4096U
+#define MEXT_TEST_BLK_BITS 12U
+#define MEXT_TEST_BLOCKS_PER_GRP 32768U
+#define MEXT_TEST_NGROUPS 8U
+#define MEXT_TEST_FIRST_DATA_BLK 1U
+#define MEXT_TEST_TOTAL_BLKS (MEXT_TEST_FIRST_DATA_BLK + \
+ MEXT_TEST_NGROUPS * \
+ MEXT_TEST_BLOCKS_PER_GRP)
+#define MEXT_MAX_META_BLKS 16U
+#define MEXT_META_BASE_PBLK 1000U
+
+struct mext_meta_blk {
+ ext4_fsblk_t pblk;
+ struct buffer_head bh;
+ u8 data[MEXT_TEST_BLK_SIZE];
+ bool in_use;
+};
+
+struct mext_test_ctx {
+ struct super_block *sb;
+ struct ext4_sb_info sbi;
+ struct ext4_super_block *es;
+ struct blockgroup_lock bgl;
+ struct gendisk bdev_disk;
+ struct block_device bdev;
+ struct request_queue bdev_queue;
+ struct address_space bdev_mapping;
+ struct buffer_head sbh;
+ u8 sbh_data[MEXT_TEST_BLK_SIZE];
+ struct mext_meta_blk *meta_blks;
+ ext4_fsblk_t next_meta_pblk;
+ struct inode *orig_inode;
+ struct inode *donor_inode;
+ struct dentry orig_dentry;
+ struct dentry donor_dentry;
+ struct file orig_file;
+ struct file donor_file;
+ bool es_shrinker_inited;
+ bool counters_inited;
+};
+
+static struct kunit *active_mext_test;
+
+static void mext_noop_work(struct work_struct *work)
+{
+}
+
+static void mext_noop_timer(struct timer_list *t)
+{
+}
+
+static unsigned long mext_dummy_shrinker_count(struct shrinker *shrink,
+ struct shrink_control *sc)
+{
+ return 0;
+}
+
+static unsigned long mext_dummy_shrinker_scan(struct shrinker *shrink,
+ struct shrink_control *sc)
+{
+ return SHRINK_STOP;
+}
+
+static struct inode *mext_test_alloc_inode(struct super_block *sb)
+{
+ struct ext4_inode_info *ei;
+
+ ei = kzalloc_obj(*ei);
+ if (!ei)
+ return NULL;
+
+ init_rwsem(&ei->i_data_sem);
+ inode_init_once(&ei->vfs_inode);
+ inode_set_iversion(&ei->vfs_inode, 1);
+ ei->i_prealloc_node = RB_ROOT;
+ rwlock_init(&ei->i_prealloc_lock);
+ ext4_es_init_tree(&ei->i_es_tree);
+ rwlock_init(&ei->i_es_lock);
+ INIT_LIST_HEAD(&ei->i_es_list);
+ spin_lock_init(&ei->i_raw_lock);
+ spin_lock_init(&ei->i_block_reservation_lock);
+ ext4_init_pending_tree(&ei->i_pending_tree);
+ init_rwsem(&ei->xattr_sem);
+ INIT_LIST_HEAD(&ei->i_rsv_conversion_list);
+ spin_lock_init(&ei->i_completed_io_lock);
+ INIT_LIST_HEAD(&ei->i_orphan);
+ ext4_fc_init_inode(&ei->vfs_inode);
+ spin_lock_init(&ei->i_fc_lock);
+ ei->i_projid = make_kprojid(&init_user_ns, EXT4_DEF_PROJID);
+ ei->i_extra_isize = sizeof(struct ext4_inode) - EXT4_GOOD_OLD_INODE_SIZE;
+ ei->i_last_alloc_group = ~0U;
+
+ return &ei->vfs_inode;
+}
+
+static void mext_test_free_inode(struct inode *inode)
+{
+ kfree(EXT4_I(inode));
+}
+
+static void mext_test_evict_inode(struct inode *inode)
+{
+ truncate_inode_pages_final(&inode->i_data);
+ ext4_es_remove_extent(inode, 0, EXT_MAX_BLOCKS);
+ clear_inode(inode);
+}
+
+#ifdef CONFIG_QUOTA
+static struct dquot __rcu **mext_get_dquots(struct inode *inode)
+{
+ return EXT4_I(inode)->i_dquot;
+}
+
+static qsize_t *mext_get_reserved_space(struct inode *inode)
+{
+ return &EXT4_I(inode)->i_reserved_quota;
+}
+
+static const struct dquot_operations mext_dummy_qops = {
+ .get_reserved_space = mext_get_reserved_space,
+};
+#endif
+
+static const struct super_operations mext_test_sops = {
+ .alloc_inode = mext_test_alloc_inode,
+ .free_inode = mext_test_free_inode,
+ .evict_inode = mext_test_evict_inode,
+#ifdef CONFIG_QUOTA
+ .get_dquots = mext_get_dquots,
+#endif
+};
+
+static int mext_init_fs_context(struct fs_context *fc)
+{
+ return 0;
+}
+
+static int mext_set_super(struct super_block *sb, struct fs_context *fc)
+{
+ return set_anon_super_fc(sb, fc);
+}
+
+static struct file_system_type mext_test_fs_type = {
+ .name = "ext4_move_extent_test",
+ .init_fs_context = mext_init_fs_context,
+ .kill_sb = kill_anon_super,
+};
+
+static int mext_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 mext_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 mext_mark_inode_dirty_stub(handle_t *handle, struct inode *inode,
+ const char *func, unsigned int line)
+{
+ return 0;
+}
+
+static struct buffer_head *mext_ext_getblk_stub(struct inode *inode,
+ ext4_fsblk_t pblk,
+ gfp_t gfp_flags)
+{
+ struct kunit *test = active_mext_test;
+ struct mext_test_ctx *ctx;
+ struct mext_meta_blk *slot = NULL;
+ unsigned int i;
+
+ if (!test)
+ return NULL;
+ ctx = test->priv;
+ if (!ctx || !ctx->meta_blks)
+ return NULL;
+
+ for (i = 0; i < MEXT_MAX_META_BLKS; i++) {
+ if (ctx->meta_blks[i].in_use &&
+ ctx->meta_blks[i].pblk == pblk) {
+ slot = &ctx->meta_blks[i];
+ break;
+ }
+ }
+
+ if (!slot) {
+ for (i = 0; i < MEXT_MAX_META_BLKS; i++) {
+ if (!ctx->meta_blks[i].in_use) {
+ slot = &ctx->meta_blks[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 mext_mb_new_blocks_stub(handle_t *handle,
+ struct ext4_allocation_request *ar,
+ int *errp)
+{
+ struct kunit *test = active_mext_test;
+ struct mext_test_ctx *ctx;
+ ext4_fsblk_t pblk;
+
+ if (!test) {
+ *errp = -EIO;
+ return 0;
+ }
+ ctx = test->priv;
+ pblk = ctx->next_meta_pblk;
+ ctx->next_meta_pblk += ar->len;
+ *errp = 0;
+ return pblk;
+}
+
+static void mext_free_blocks_stub(handle_t *handle, struct inode *inode,
+ struct buffer_head *bh, ext4_fsblk_t block,
+ unsigned long count, int flags)
+{
+}
+
+static int mext_journal_data_mode_stub(struct inode *inode)
+{
+ return EXT4_INODE_JOURNAL_DATA_MODE;
+}
+
+static void mext_init_mock_file(struct file *filp, struct dentry *dentry,
+ struct inode *inode)
+{
+ struct file tmp = {
+ .f_path = { .dentry = dentry },
+ .f_inode = inode,
+ .f_mapping = inode->i_mapping,
+ .f_mode = FMODE_READ | FMODE_WRITE | FMODE_NOCMTIME,
+ };
+
+ memset(dentry, 0, sizeof(*dentry));
+ dentry->d_inode = inode;
+ memcpy(filp, &tmp, sizeof(*filp));
+}
+
+static struct inode *mext_create_test_inode(struct kunit *test,
+ struct super_block *sb,
+ unsigned long ino,
+ ext4_lblk_t size_blks)
+{
+ struct ext4_extent_header *eh;
+ struct inode *inode;
+
+ inode = new_inode(sb);
+ KUNIT_ASSERT_NOT_NULL(test, inode);
+
+ inode->i_ino = ino;
+ inode->i_mode = S_IFREG | 0644;
+ set_nlink(inode, 1);
+ inode->i_blkbits = sb->s_blocksize_bits;
+ /*
+ * Note: leave S_NOQUOTA clear so ext4_is_quota_file(inode) is false.
+ */
+ inode->i_flags |= S_NOSEC;
+ inode_state_set_raw(inode,
+ I_DIRTY_SYNC | I_DIRTY_DATASYNC | I_DIRTY_PAGES);
+ mapping_set_gfp_mask(&inode->i_data, GFP_KERNEL);
+
+ EXT4_I(inode)->i_csum_seed = 0x13572468U;
+ EXT4_I(inode)->i_disksize = (loff_t)size_blks << sb->s_blocksize_bits;
+ i_size_write(inode, EXT4_I(inode)->i_disksize);
+ ext4_set_inode_flag(inode, EXT4_INODE_EXTENTS);
+ ext4_set_aops(inode);
+
+ eh = ext_inode_hdr(inode);
+ memset(EXT4_I(inode)->i_data, 0, sizeof(EXT4_I(inode)->i_data));
+ eh->eh_magic = EXT4_EXT_MAGIC;
+ eh->eh_entries = 0;
+ eh->eh_max = cpu_to_le16(ext4_ext_space_root_idx_test(inode, 0));
+ eh->eh_depth = 0;
+
+ return inode;
+}
+
+static void mext_insert_extent(struct kunit *test, struct inode *inode,
+ ext4_lblk_t lblk, u16 len, ext4_fsblk_t pblk,
+ bool unwritten)
+{
+ struct ext4_extent newex = {};
+ struct ext4_ext_path *path;
+
+ newex.ee_block = cpu_to_le32(lblk);
+ newex.ee_len = cpu_to_le16(len);
+ if (unwritten)
+ ext4_ext_mark_unwritten(&newex);
+ ext4_ext_store_pblock(&newex, pblk);
+
+ inode_lock(inode);
+ down_write(&EXT4_I(inode)->i_data_sem);
+ path = ext4_find_extent(inode, lblk, NULL, 0);
+ if (!IS_ERR(path))
+ path = ext4_ext_insert_extent(NULL, inode, path, &newex, 0);
+ up_write(&EXT4_I(inode)->i_data_sem);
+ inode_unlock(inode);
+
+ KUNIT_ASSERT_FALSE(test, IS_ERR(path));
+ ext4_free_ext_path(path);
+}
+
+static void mext_populate_folio_block(struct kunit *test, struct inode *inode,
+ ext4_lblk_t lblk, u8 pattern)
+{
+ loff_t pos = (loff_t)lblk << inode->i_blkbits;
+ size_t bsize = i_blocksize(inode);
+ struct folio *folio;
+ u8 *addr;
+
+ folio = __filemap_get_folio(inode->i_mapping, pos >> PAGE_SHIFT,
+ FGP_CREAT | FGP_LOCK, GFP_KERNEL);
+ KUNIT_ASSERT_FALSE(test, IS_ERR(folio));
+
+ addr = folio_address(folio);
+ memset(addr + offset_in_folio(folio, pos), pattern, bsize);
+ folio_mark_uptodate(folio);
+ folio_unlock(folio);
+ folio_put(folio);
+}
+
+static void mext_assert_mapped_block(struct kunit *test, struct inode *inode,
+ ext4_lblk_t lblk, ext4_fsblk_t exp_pblk,
+ unsigned int exp_flag)
+{
+ struct ext4_map_blocks map = {
+ .m_lblk = lblk,
+ .m_len = 1,
+ };
+ int ret;
+
+ inode_lock(inode);
+ ret = ext4_map_blocks(NULL, inode, &map, 0);
+ inode_unlock(inode);
+
+ KUNIT_EXPECT_EQ(test, ret, 1);
+ KUNIT_EXPECT_EQ(test, map.m_pblk, exp_pblk);
+ KUNIT_EXPECT_TRUE(test, (map.m_flags & exp_flag) != 0);
+ if (exp_flag == EXT4_MAP_MAPPED)
+ KUNIT_EXPECT_EQ(test, map.m_flags & EXT4_MAP_UNWRITTEN, 0U);
+ else if (exp_flag == EXT4_MAP_UNWRITTEN)
+ KUNIT_EXPECT_EQ(test, map.m_flags & EXT4_MAP_MAPPED, 0U);
+}
+
+static void mext_cleanup_ctx(void *data)
+{
+ struct mext_test_ctx *ctx = data;
+
+ active_mext_test = NULL;
+
+ if (ctx->orig_inode) {
+ iput(ctx->orig_inode);
+ ctx->orig_inode = NULL;
+ }
+ if (ctx->donor_inode) {
+ iput(ctx->donor_inode);
+ ctx->donor_inode = NULL;
+ }
+
+ cancel_work_sync(&ctx->sbi.s_sb_upd_work);
+ timer_delete_sync(&ctx->sbi.s_err_report);
+
+ if (ctx->es_shrinker_inited) {
+ ext4_es_unregister_shrinker(&ctx->sbi);
+ ctx->es_shrinker_inited = false;
+ }
+ if (ctx->counters_inited) {
+ percpu_counter_destroy(&ctx->sbi.s_freeclusters_counter);
+ percpu_counter_destroy(&ctx->sbi.s_dirtyclusters_counter);
+ ctx->counters_inited = false;
+ }
+
+ if (ctx->sb) {
+ ctx->sb->s_fs_info = NULL;
+ deactivate_super(ctx->sb);
+ ctx->sb = NULL;
+ }
+ if (ctx->bdev.bd_stats) {
+ free_percpu(ctx->bdev.bd_stats);
+ ctx->bdev.bd_stats = NULL;
+ }
+}
+
+static struct mext_test_ctx *mext_setup_ctx(struct kunit *test)
+{
+ struct mext_test_ctx *ctx;
+ struct fs_context *fc;
+ struct super_block *sb;
+ int err;
+
+ ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, ctx);
+ ctx->es = kunit_kzalloc(test, sizeof(*ctx->es), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, ctx->es);
+ ctx->meta_blks = kunit_kcalloc(test, MEXT_MAX_META_BLKS,
+ sizeof(*ctx->meta_blks), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, ctx->meta_blks);
+ ctx->next_meta_pblk = MEXT_META_BASE_PBLK;
+
+ fc = fs_context_for_mount(&mext_test_fs_type, 0);
+ KUNIT_ASSERT_FALSE(test, IS_ERR(fc));
+ sb = sget_fc(fc, NULL, mext_set_super);
+ put_fs_context(fc);
+ KUNIT_ASSERT_FALSE(test, IS_ERR(sb));
+ up_write(&sb->s_umount);
+ ctx->sb = sb;
+
+ INIT_WORK(&ctx->sbi.s_sb_upd_work, mext_noop_work);
+ timer_setup(&ctx->sbi.s_err_report, mext_noop_timer, 0);
+
+ err = kunit_add_action_or_reset(test, mext_cleanup_ctx, ctx);
+ KUNIT_ASSERT_EQ(test, err, 0);
+
+ ctx->bdev.bd_stats = alloc_percpu(struct disk_stats);
+ KUNIT_ASSERT_NOT_NULL(test, ctx->bdev.bd_stats);
+ ctx->bdev_disk.queue = &ctx->bdev_queue;
+ ctx->bdev.bd_disk = &ctx->bdev_disk;
+ ctx->bdev.bd_queue = &ctx->bdev_queue;
+ ctx->bdev.bd_mapping = &ctx->bdev_mapping;
+ bdev_set_flag(&ctx->bdev, BD_READ_ONLY);
+
+ sb->s_bdev = &ctx->bdev;
+ sb->s_blocksize = MEXT_TEST_BLK_SIZE;
+ sb->s_blocksize_bits = MEXT_TEST_BLK_BITS;
+ sb->s_maxbytes = MAX_LFS_FILESIZE;
+ sb->s_magic = EXT4_SUPER_MAGIC;
+ sb->s_op = &mext_test_sops;
+#ifdef CONFIG_QUOTA
+ sb->dq_op = &mext_dummy_qops;
+#endif
+#ifdef CONFIG_FS_ENCRYPTION
+ sb->s_cop = &ext4_cryptops;
+#endif
+ sb->s_fs_info = &ctx->sbi;
+ strscpy(sb->s_id, "ext4_mext_kunit", sizeof(sb->s_id));
+
+ ctx->sbh.b_data = ctx->sbh_data;
+ ctx->sbh.b_size = MEXT_TEST_BLK_SIZE;
+ ctx->sbh.b_bdev = &ctx->bdev;
+ atomic_set(&ctx->sbh.b_count, 2);
+ set_buffer_uptodate(&ctx->sbh);
+ clear_buffer_mapped(&ctx->sbh);
+
+ ctx->es->s_magic = cpu_to_le16(EXT4_SUPER_MAGIC);
+ ctx->es->s_rev_level = cpu_to_le32(EXT4_DYNAMIC_REV);
+ ctx->es->s_first_data_block = cpu_to_le32(MEXT_TEST_FIRST_DATA_BLK);
+ ctx->es->s_log_block_size = cpu_to_le32(2);
+ ctx->es->s_log_cluster_size = cpu_to_le32(2);
+ ctx->es->s_blocks_per_group = cpu_to_le32(MEXT_TEST_BLOCKS_PER_GRP);
+ ctx->es->s_clusters_per_group = cpu_to_le32(MEXT_TEST_BLOCKS_PER_GRP);
+ ctx->es->s_inodes_per_group = cpu_to_le32(8192);
+ ctx->es->s_inodes_count = cpu_to_le32(MEXT_TEST_NGROUPS * 8192);
+ ctx->es->s_feature_incompat = cpu_to_le32(EXT4_FEATURE_INCOMPAT_EXTENTS);
+ ext4_blocks_count_set(ctx->es, MEXT_TEST_TOTAL_BLKS);
+
+ bgl_lock_init(&ctx->bgl);
+ ctx->sbi.s_sb = sb;
+ ctx->sbi.s_es = ctx->es;
+ ctx->sbi.s_sbh = &ctx->sbh;
+ ctx->sbi.s_blockgroup_lock = &ctx->bgl;
+ ctx->sbi.s_blocks_per_group = MEXT_TEST_BLOCKS_PER_GRP;
+ ctx->sbi.s_clusters_per_group = MEXT_TEST_BLOCKS_PER_GRP;
+ ctx->sbi.s_cluster_ratio = 1;
+ ctx->sbi.s_cluster_bits = 0;
+ ctx->sbi.s_groups_count = MEXT_TEST_NGROUPS;
+ ctx->sbi.s_desc_size = EXT4_MIN_DESC_SIZE;
+ ctx->sbi.s_desc_per_block = MEXT_TEST_BLK_SIZE / EXT4_MIN_DESC_SIZE;
+ ctx->sbi.s_inodes_per_group = 8192;
+ ctx->sbi.s_inode_size = sizeof(struct ext4_inode);
+ ctx->sbi.s_inodes_per_block = MEXT_TEST_BLK_SIZE /
+ sizeof(struct ext4_inode);
+ ctx->sbi.s_bitmap_maxbytes = MAX_LFS_FILESIZE;
+ set_opt(sb, ERRORS_CONT);
+
+ mutex_init(&ctx->sbi.s_orphan_lock);
+ INIT_LIST_HEAD(&ctx->sbi.s_orphan);
+ spin_lock_init(&ctx->sbi.s_bdev_wb_lock);
+ spin_lock_init(&ctx->sbi.s_error_lock);
+ spin_lock_init(&ctx->sbi.s_md_lock);
+ INIT_LIST_HEAD(&ctx->sbi.s_fc_q[FC_Q_MAIN]);
+ INIT_LIST_HEAD(&ctx->sbi.s_fc_q[FC_Q_STAGING]);
+ INIT_LIST_HEAD(&ctx->sbi.s_fc_dentry_q[FC_Q_MAIN]);
+ INIT_LIST_HEAD(&ctx->sbi.s_fc_dentry_q[FC_Q_STAGING]);
+ mutex_init(&ctx->sbi.s_fc_lock);
+ ratelimit_state_init(&ctx->sbi.s_err_ratelimit_state,
+ DEFAULT_RATELIMIT_INTERVAL,
+ DEFAULT_RATELIMIT_BURST);
+ ratelimit_state_init(&ctx->sbi.s_warning_ratelimit_state,
+ DEFAULT_RATELIMIT_INTERVAL,
+ DEFAULT_RATELIMIT_BURST);
+ ratelimit_state_init(&ctx->sbi.s_msg_ratelimit_state,
+ DEFAULT_RATELIMIT_INTERVAL,
+ DEFAULT_RATELIMIT_BURST);
+
+ err = percpu_counter_init(&ctx->sbi.s_freeclusters_counter, 65536,
+ GFP_KERNEL);
+ KUNIT_ASSERT_EQ(test, err, 0);
+ err = percpu_counter_init(&ctx->sbi.s_dirtyclusters_counter, 0,
+ GFP_KERNEL);
+ if (err) {
+ percpu_counter_destroy(&ctx->sbi.s_freeclusters_counter);
+ KUNIT_FAIL_AND_ABORT(test, "dirtyclusters counter init failed");
+ }
+ ctx->counters_inited = true;
+
+ err = ext4_es_register_shrinker(&ctx->sbi);
+ KUNIT_ASSERT_EQ(test, err, 0);
+ ctx->sbi.s_es_shrinker->count_objects = mext_dummy_shrinker_count;
+ ctx->sbi.s_es_shrinker->scan_objects = mext_dummy_shrinker_scan;
+ ctx->es_shrinker_inited = true;
+
+ ctx->orig_inode = mext_create_test_inode(test, sb, 21, 16);
+ ctx->donor_inode = mext_create_test_inode(test, sb, 22, 16);
+ mext_init_mock_file(&ctx->orig_file, &ctx->orig_dentry,
+ ctx->orig_inode);
+ mext_init_mock_file(&ctx->donor_file, &ctx->donor_dentry,
+ ctx->donor_inode);
+
+ test->priv = ctx;
+ active_mext_test = test;
+
+ kunit_activate_static_stub(test, __ext4_ext_dirty,
+ mext_ext_dirty_stub);
+ kunit_activate_static_stub(test, __ext4_handle_dirty_metadata,
+ mext_handle_dirty_metadata_stub);
+ kunit_activate_static_stub(test, __ext4_mark_inode_dirty,
+ mext_mark_inode_dirty_stub);
+ kunit_activate_static_stub(test, ext4_ext_getblk,
+ mext_ext_getblk_stub);
+ kunit_activate_static_stub(test, ext4_mb_new_blocks,
+ mext_mb_new_blocks_stub);
+ kunit_activate_static_stub(test, ext4_free_blocks,
+ mext_free_blocks_stub);
+
+ return ctx;
+}
+
+static void test_double_data_sem_lock_order(struct kunit *test)
+{
+ struct mext_test_ctx *ctx = mext_setup_ctx(test);
+ struct inode *low = ctx->orig_inode;
+ struct inode *high = ctx->donor_inode;
+
+ if (low > high)
+ swap(low, high);
+
+ /* Acquire with first < second */
+ ext4_double_down_write_data_sem(low, high);
+ KUNIT_EXPECT_TRUE(test, rwsem_is_locked(&EXT4_I(low)->i_data_sem));
+ KUNIT_EXPECT_TRUE(test, rwsem_is_locked(&EXT4_I(high)->i_data_sem));
+ ext4_double_up_write_data_sem(low, high);
+ KUNIT_EXPECT_FALSE(test, rwsem_is_locked(&EXT4_I(low)->i_data_sem));
+ KUNIT_EXPECT_FALSE(test, rwsem_is_locked(&EXT4_I(high)->i_data_sem));
+
+ /* Acquire with first > second */
+ ext4_double_down_write_data_sem(high, low);
+ KUNIT_EXPECT_TRUE(test, rwsem_is_locked(&EXT4_I(low)->i_data_sem));
+ KUNIT_EXPECT_TRUE(test, rwsem_is_locked(&EXT4_I(high)->i_data_sem));
+ ext4_double_up_write_data_sem(high, low);
+ KUNIT_EXPECT_FALSE(test, rwsem_is_locked(&EXT4_I(low)->i_data_sem));
+ KUNIT_EXPECT_FALSE(test, rwsem_is_locked(&EXT4_I(high)->i_data_sem));
+}
+
+static void test_mext_check_validity_rejections(struct kunit *test)
+{
+ struct mext_test_ctx *ctx = mext_setup_ctx(test);
+ struct inode *orig = ctx->orig_inode;
+ struct inode *donor = ctx->donor_inode;
+ struct super_block *other_sb;
+ __u64 moved_len = 99;
+ int ret;
+
+ /* Valid baseline */
+ KUNIT_EXPECT_EQ(test, ext4_mext_check_validity_test(orig, donor), 0);
+
+ /* Same inode rejected both directly and via ext4_move_extents() */
+ KUNIT_EXPECT_EQ(test, ext4_mext_check_validity_test(orig, orig),
+ -EINVAL);
+ ret = ext4_move_extents(&ctx->orig_file, &ctx->orig_file, 0, 0, 1,
+ &moved_len);
+ KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+ KUNIT_EXPECT_EQ(test, moved_len, 0ULL);
+
+ /* Cross-superblock rejected */
+ other_sb = kunit_kzalloc(test, sizeof(*other_sb), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, other_sb);
+ donor->i_sb = other_sb;
+ KUNIT_EXPECT_EQ(test, ext4_mext_check_validity_test(orig, donor),
+ -EINVAL);
+ donor->i_sb = ctx->sb;
+
+ /* Non-regular files rejected */
+ orig->i_mode = S_IFIFO | 0644;
+ KUNIT_EXPECT_EQ(test, ext4_mext_check_validity_test(orig, donor),
+ -EINVAL);
+ ret = ext4_move_extents(&ctx->orig_file, &ctx->donor_file, 0, 0, 1,
+ &moved_len);
+ KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+ orig->i_mode = S_IFREG | 0644;
+
+ donor->i_mode = S_IFDIR | 0755;
+ KUNIT_EXPECT_EQ(test, ext4_mext_check_validity_test(orig, donor),
+ -EINVAL);
+ donor->i_mode = S_IFREG | 0644;
+
+ /* Bigalloc feature rejected */
+ ext4_set_feature_bigalloc(ctx->sb);
+ KUNIT_EXPECT_EQ(test, ext4_mext_check_validity_test(orig, donor),
+ -EOPNOTSUPP);
+ ext4_clear_feature_bigalloc(ctx->sb);
+
+#if IS_ENABLED(CONFIG_FS_DAX)
+ /* DAX inode rejected */
+ orig->i_flags |= S_DAX;
+ KUNIT_EXPECT_EQ(test, ext4_mext_check_validity_test(orig, donor),
+ -EOPNOTSUPP);
+ orig->i_flags &= ~S_DAX;
+#endif
+
+ /* Data journaling mode rejected */
+ kunit_activate_static_stub(test, ext4_inode_journal_mode,
+ mext_journal_data_mode_stub);
+ KUNIT_EXPECT_EQ(test, ext4_mext_check_validity_test(orig, donor),
+ -EOPNOTSUPP);
+ kunit_deactivate_static_stub(test, ext4_inode_journal_mode);
+
+ /* Encrypted inode rejected */
+ donor->i_flags |= S_ENCRYPTED;
+ KUNIT_EXPECT_EQ(test, ext4_mext_check_validity_test(orig, donor),
+ -EOPNOTSUPP);
+ donor->i_flags &= ~S_ENCRYPTED;
+
+ /* Non-extent inode rejected */
+ ext4_clear_inode_flag(orig, EXT4_INODE_EXTENTS);
+ KUNIT_EXPECT_EQ(test, ext4_mext_check_validity_test(orig, donor),
+ -EOPNOTSUPP);
+ ext4_set_inode_flag(orig, EXT4_INODE_EXTENTS);
+
+ /* SUID / SGID on donor rejected */
+ donor->i_mode |= S_ISUID;
+ KUNIT_EXPECT_EQ(test, ext4_mext_check_validity_test(orig, donor),
+ -EINVAL);
+ donor->i_mode = (donor->i_mode & ~S_ISUID) | S_ISGID;
+ KUNIT_EXPECT_EQ(test, ext4_mext_check_validity_test(orig, donor),
+ -EINVAL);
+ donor->i_mode &= ~S_ISGID;
+
+ /* Immutable / append-only donor rejected with -EPERM */
+ donor->i_flags |= S_IMMUTABLE;
+ KUNIT_EXPECT_EQ(test, ext4_mext_check_validity_test(orig, donor),
+ -EPERM);
+ donor->i_flags = (donor->i_flags & ~S_IMMUTABLE) | S_APPEND;
+ KUNIT_EXPECT_EQ(test, ext4_mext_check_validity_test(orig, donor),
+ -EPERM);
+ donor->i_flags &= ~S_APPEND;
+
+ /* Swapfile rejected with -ETXTBSY */
+ orig->i_flags |= S_SWAPFILE;
+ KUNIT_EXPECT_EQ(test, ext4_mext_check_validity_test(orig, donor),
+ -ETXTBSY);
+ orig->i_flags &= ~S_SWAPFILE;
+
+ /* Quota file (S_NOQUOTA without EXT4_INODE_EA_INODE) rejected */
+ orig->i_flags |= S_NOQUOTA;
+ KUNIT_EXPECT_EQ(test, ext4_mext_check_validity_test(orig, donor),
+ -EOPNOTSUPP);
+ ext4_set_inode_flag(orig, EXT4_INODE_EA_INODE);
+ KUNIT_EXPECT_EQ(test, ext4_mext_check_validity_test(orig, donor), 0);
+ ext4_clear_inode_flag(orig, EXT4_INODE_EA_INODE);
+ orig->i_flags &= ~S_NOQUOTA;
+
+ /* Zero-byte file rejected with -EINVAL */
+ i_size_write(donor, 0);
+ KUNIT_EXPECT_EQ(test, ext4_mext_check_validity_test(orig, donor),
+ -EINVAL);
+ i_size_write(donor, EXT4_I(donor)->i_disksize);
+}
+
+static void test_mext_check_adjust_range(struct kunit *test)
+{
+ struct mext_test_ctx *ctx = mext_setup_ctx(test);
+ struct inode *orig = ctx->orig_inode;
+ struct inode *donor = ctx->donor_inode;
+ __u64 len;
+ int ret;
+
+ /* Sub-page block alignment check when blocksize (1024) < PAGE_SIZE */
+ orig->i_blkbits = 10;
+ donor->i_blkbits = 10;
+ i_size_write(orig, 32 * 1024);
+ i_size_write(donor, 32 * 1024);
+
+ len = 4;
+ ret = ext4_mext_check_adjust_range_test(orig, donor, 1, 2, &len);
+ KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+
+ len = 4;
+ ret = ext4_mext_check_adjust_range_test(orig, donor, 1,
+ 1 + (PAGE_SIZE >> 10), &len);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, len, 4ULL);
+
+ orig->i_blkbits = MEXT_TEST_BLK_BITS;
+ donor->i_blkbits = MEXT_TEST_BLK_BITS;
+
+ /* EXT_MAX_BLOCKS boundary and sum-overflow checks */
+ len = 1;
+ KUNIT_EXPECT_EQ(test,
+ ext4_mext_check_adjust_range_test(orig, donor,
+ EXT_MAX_BLOCKS,
+ EXT_MAX_BLOCKS, &len),
+ -EINVAL);
+
+ len = (u64)EXT_MAX_BLOCKS + 1;
+ KUNIT_EXPECT_EQ(test,
+ ext4_mext_check_adjust_range_test(orig, donor, 0, 0,
+ &len),
+ -EINVAL);
+
+ len = 4;
+ KUNIT_EXPECT_EQ(test,
+ ext4_mext_check_adjust_range_test(orig, donor,
+ EXT_MAX_BLOCKS - 4,
+ EXT_MAX_BLOCKS - 4,
+ &len),
+ -EINVAL);
+
+ /* Clamping to shorter EOF (including partial trailing block i_size) */
+ i_size_write(orig, 5 * MEXT_TEST_BLK_SIZE + 100); /* orig_eof = 6 */
+ i_size_write(donor, 4 * MEXT_TEST_BLK_SIZE); /* donor_eof = 4 */
+
+ len = 12;
+ ret = ext4_mext_check_adjust_range_test(orig, donor, 1, 1, &len);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, len, 3ULL);
+
+ /* Start at or beyond EOF clamps len to 0 and returns -EINVAL */
+ len = 4;
+ ret = ext4_mext_check_adjust_range_test(orig, donor, 4, 4, &len);
+ KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+ KUNIT_EXPECT_EQ(test, len, 0ULL);
+}
+
+static void test_move_extents_holes_and_unwritten(struct kunit *test)
+{
+ struct mext_test_ctx *ctx = mext_setup_ctx(test);
+ struct inode *orig = ctx->orig_inode;
+ struct inode *donor = ctx->donor_inode;
+ __u64 moved_len = 0;
+ int ret;
+
+ /*
+ * orig: [0, 2) hole, [2, 4) unwritten -> pblk 202..203,
+ * [4, 6) unwritten -> pblk 204..205
+ * donor: [0, 2) unwritten -> pblk 300..301, [2, 4) hole,
+ * [4, 6) unwritten -> pblk 304..305
+ *
+ * Expected across [0, 6) (clamped from requested len = 16 to EOF = 6):
+ * - [0, 2): skipped in ext4_move_extents() because orig is a hole
+ * - [2, 4): skipped via MEXT_SKIP_EXTENT because donor is a hole
+ * - [4, 6): swapped via MEXT_MOVE_EXTENT (both unwritten)
+ */
+ i_size_write(orig, 6 * MEXT_TEST_BLK_SIZE);
+ EXT4_I(orig)->i_disksize = 6 * MEXT_TEST_BLK_SIZE;
+ i_size_write(donor, 6 * MEXT_TEST_BLK_SIZE);
+ EXT4_I(donor)->i_disksize = 6 * MEXT_TEST_BLK_SIZE;
+
+ mext_insert_extent(test, orig, 2, 4, 202, true);
+ mext_insert_extent(test, donor, 0, 2, 300, true);
+ mext_insert_extent(test, donor, 4, 2, 304, true);
+
+ ret = ext4_move_extents(&ctx->orig_file, &ctx->donor_file, 0, 0, 16,
+ &moved_len);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, moved_len, 2ULL);
+
+ /* [2, 4) on orig remained on pblk 202..203; [4, 6) swapped to 304..305 */
+ mext_assert_mapped_block(test, orig, 2, 202, EXT4_MAP_UNWRITTEN);
+ mext_assert_mapped_block(test, orig, 3, 203, EXT4_MAP_UNWRITTEN);
+ mext_assert_mapped_block(test, orig, 4, 304, EXT4_MAP_UNWRITTEN);
+ mext_assert_mapped_block(test, orig, 5, 305, EXT4_MAP_UNWRITTEN);
+
+ /* [0, 2) on donor remained 300..301; [4, 6) received 204..205 */
+ mext_assert_mapped_block(test, donor, 0, 300, EXT4_MAP_UNWRITTEN);
+ mext_assert_mapped_block(test, donor, 4, 204, EXT4_MAP_UNWRITTEN);
+ mext_assert_mapped_block(test, donor, 5, 205, EXT4_MAP_UNWRITTEN);
+}
+
+static void test_move_extents_written_copy_data_and_split(struct kunit *test)
+{
+ struct mext_test_ctx *ctx = mext_setup_ctx(test);
+ struct inode *orig = ctx->orig_inode;
+ struct inode *donor = ctx->donor_inode;
+ ext4_lblk_t lblk;
+ __u64 moved_len = 0;
+ int ret;
+
+ /*
+ * orig: [0, 6) written -> pblk 400..405, uptodate folios pre-filled
+ * donor: [1, 2) written -> pblk 501, [2, 3) hole,
+ * [3, 5) unwritten -> pblk 503..504
+ *
+ * Moving [1, 5) exercises MEXT_COPY_DATA, left/right extent splits on
+ * orig, donor hole skipping at lblk 2, written preservation on orig,
+ * mark_unwritten=1 on donor, and page cache data preservation.
+ */
+ i_size_write(orig, 6 * MEXT_TEST_BLK_SIZE);
+ EXT4_I(orig)->i_disksize = 6 * MEXT_TEST_BLK_SIZE;
+ i_size_write(donor, 6 * MEXT_TEST_BLK_SIZE);
+ EXT4_I(donor)->i_disksize = 6 * MEXT_TEST_BLK_SIZE;
+
+ mext_insert_extent(test, orig, 0, 6, 400, false);
+ mext_insert_extent(test, donor, 1, 1, 501, false);
+ mext_insert_extent(test, donor, 3, 2, 503, true);
+
+ for (lblk = 0; lblk < 6; lblk++)
+ mext_populate_folio_block(test, orig, lblk, 0xA0 + lblk);
+
+ ret = ext4_move_extents(&ctx->orig_file, &ctx->donor_file, 1, 1, 4,
+ &moved_len);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, moved_len, 3ULL);
+
+ /* Verify orig mappings: 0, 2, 5 unchanged; 1 -> 501, 3..4 -> 503..504 */
+ mext_assert_mapped_block(test, orig, 0, 400, EXT4_MAP_MAPPED);
+ mext_assert_mapped_block(test, orig, 1, 501, EXT4_MAP_MAPPED);
+ mext_assert_mapped_block(test, orig, 2, 402, EXT4_MAP_MAPPED);
+ mext_assert_mapped_block(test, orig, 3, 503, EXT4_MAP_MAPPED);
+ mext_assert_mapped_block(test, orig, 4, 504, EXT4_MAP_MAPPED);
+ mext_assert_mapped_block(test, orig, 5, 405, EXT4_MAP_MAPPED);
+
+ /* Donor's swapped extents are marked unwritten to avoid leaking data */
+ mext_assert_mapped_block(test, donor, 1, 401, EXT4_MAP_UNWRITTEN);
+ mext_assert_mapped_block(test, donor, 3, 403, EXT4_MAP_UNWRITTEN);
+ mext_assert_mapped_block(test, donor, 4, 404, EXT4_MAP_UNWRITTEN);
+
+ /* Verify cached bytes in orig's moved folios were preserved & dirtied */
+ for (lblk = 1; lblk <= 4; lblk++) {
+ loff_t pos = (loff_t)lblk << orig->i_blkbits;
+ struct folio *folio;
+ u8 *addr;
+
+ if (lblk == 2)
+ continue;
+ folio = filemap_get_folio(orig->i_mapping, pos >> PAGE_SHIFT);
+ KUNIT_ASSERT_FALSE(test, IS_ERR(folio));
+ KUNIT_EXPECT_TRUE(test, folio_test_uptodate(folio));
+ KUNIT_EXPECT_TRUE(test, folio_test_dirty(folio));
+ addr = folio_address(folio);
+ KUNIT_EXPECT_EQ(test, addr[offset_in_folio(folio, pos)],
+ (u8)(0xA0 + lblk));
+ folio_put(folio);
+ }
+}
+
+static void test_move_extents_unwritten_orig_and_ebusy(struct kunit *test)
+{
+ struct mext_test_ctx *ctx = mext_setup_ctx(test);
+ struct inode *orig = ctx->orig_inode;
+ struct inode *donor = ctx->donor_inode;
+ struct folio *donor_folio;
+ __u64 moved_len = 0;
+ int ret;
+
+ /*
+ * Part 1: orig unwritten [0, 2) -> 600..601 (no pre-populated folios),
+ * donor written [0, 2) -> 700..701. Exercises MEXT_COPY_DATA through
+ * mext_folio_mkuptodate()'s non-uptodate unwritten buffer zeroing path.
+ */
+ i_size_write(orig, 4 * MEXT_TEST_BLK_SIZE);
+ EXT4_I(orig)->i_disksize = 4 * MEXT_TEST_BLK_SIZE;
+ i_size_write(donor, 4 * MEXT_TEST_BLK_SIZE);
+ EXT4_I(donor)->i_disksize = 4 * MEXT_TEST_BLK_SIZE;
+
+ mext_insert_extent(test, orig, 0, 2, 600, true);
+ mext_insert_extent(test, donor, 0, 2, 700, false);
+
+ ret = ext4_move_extents(&ctx->orig_file, &ctx->donor_file, 0, 0, 2,
+ &moved_len);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, moved_len, 2ULL);
+
+ mext_assert_mapped_block(test, orig, 0, 700, EXT4_MAP_UNWRITTEN);
+ mext_assert_mapped_block(test, orig, 1, 701, EXT4_MAP_UNWRITTEN);
+ mext_assert_mapped_block(test, donor, 0, 600, EXT4_MAP_UNWRITTEN);
+ mext_assert_mapped_block(test, donor, 1, 601, EXT4_MAP_UNWRITTEN);
+
+ /*
+ * Part 2: non-releasable folio on donor triggers -EBUSY from
+ * filemap_release_folio() without altering extent mappings.
+ */
+ mext_insert_extent(test, orig, 2, 1, 602, true);
+ mext_insert_extent(test, donor, 2, 1, 702, true);
+
+ donor_folio = __filemap_get_folio(donor->i_mapping,
+ ((loff_t)2 << donor->i_blkbits) >>
+ PAGE_SHIFT,
+ FGP_CREAT | FGP_LOCK, GFP_KERNEL);
+ KUNIT_ASSERT_FALSE(test, IS_ERR(donor_folio));
+ if (!folio_buffers(donor_folio))
+ create_empty_buffers(donor_folio, MEXT_TEST_BLK_SIZE, 0);
+ folio_set_checked(donor_folio);
+ folio_unlock(donor_folio);
+
+ ret = ext4_move_extents(&ctx->orig_file, &ctx->donor_file, 2, 2, 1,
+ &moved_len);
+ folio_clear_checked(donor_folio);
+ folio_put(donor_folio);
+
+ KUNIT_EXPECT_EQ(test, ret, -EBUSY);
+ KUNIT_EXPECT_EQ(test, moved_len, 0ULL);
+ mext_assert_mapped_block(test, orig, 2, 602, EXT4_MAP_UNWRITTEN);
+ mext_assert_mapped_block(test, donor, 2, 702, EXT4_MAP_UNWRITTEN);
+}
+
+static struct kunit_case move_extent_test_cases[] = {
+ KUNIT_CASE(test_double_data_sem_lock_order),
+ KUNIT_CASE(test_mext_check_validity_rejections),
+ KUNIT_CASE(test_mext_check_adjust_range),
+ KUNIT_CASE(test_move_extents_holes_and_unwritten),
+ KUNIT_CASE(test_move_extents_written_copy_data_and_split),
+ KUNIT_CASE(test_move_extents_unwritten_orig_and_ebusy),
+ {}
+};
+
+static struct kunit_suite move_extent_test_suite = {
+ .name = "ext4_move_extent_test",
+ .test_cases = move_extent_test_cases,
+};
+
+kunit_test_suites(&move_extent_test_suite);
+
+MODULE_DESCRIPTION("KUnit tests for ext4 move_extent");
+MODULE_LICENSE("GPL");
diff --git a/fs/ext4/move_extent.c b/fs/ext4/move_extent.c
index d1405e763912..49adc348dda0 100644
--- a/fs/ext4/move_extent.c
+++ b/fs/ext4/move_extent.c
@@ -40,6 +40,7 @@ ext4_double_down_write_data_sem(struct inode *first, struct inode *second)
down_write_nested(&EXT4_I(first)->i_data_sem, I_DATA_SEM_OTHER);
}
}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_double_down_write_data_sem);
/**
* ext4_double_up_write_data_sem - Release two inodes' write lock of i_data_sem
@@ -55,6 +56,7 @@ ext4_double_up_write_data_sem(struct inode *orig_inode,
up_write(&EXT4_I(orig_inode)->i_data_sem);
up_write(&EXT4_I(donor_inode)->i_data_sem);
}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_double_up_write_data_sem);
/* Grab and lock folio on both @inode1 and @inode2 by inode order. */
static int mext_folio_double_lock(struct inode *inode1, struct inode *inode2,
@@ -656,8 +658,23 @@ int ext4_move_extents(struct file *o_filp, struct file *d_filp, __u64 orig_blk,
unlock_two_nondirectories(orig_inode, donor_inode);
return ret;
}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_move_extents);
#if IS_ENABLED(CONFIG_EXT4_KUNIT_TESTS)
-EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_double_down_write_data_sem);
-EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_double_up_write_data_sem);
+int ext4_mext_check_validity_test(struct inode *orig_inode,
+ struct inode *donor_inode)
+{
+ return mext_check_validity(orig_inode, donor_inode);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_mext_check_validity_test);
+
+int ext4_mext_check_adjust_range_test(struct inode *orig_inode,
+ struct inode *donor_inode,
+ __u64 orig_start, __u64 donor_start,
+ __u64 *len)
+{
+ return mext_check_adjust_range(orig_inode, donor_inode, orig_start,
+ donor_start, len);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_mext_check_adjust_range_test);
#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 ` [PATCH 02/27] ext4: add KUnit tests for fs/ext4/extents.c Theodore Ts'o
2026-11-12 4:28 ` 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 ` Theodore Ts'o [this message]
2026-11-12 4:36 ` [PATCH 25/27] ext4: add KUnit tests for fs/ext4/move_extent.c 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-26-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