Linux EXT4 FS development
 help / color / mirror / Atom feed
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 26/27] ext4: add KUnit tests for fs/ext4/mmp.c
Date: Wed, 11 Nov 2026 23:06:37 -0500	[thread overview]
Message-ID: <20261112040638.1576836-27-tytso@mit.edu> (raw)
In-Reply-To: <20261112040638.1576836-1-tytso@mit.edu>

Add a KUnit test suite (ext4_mmp_test) covering multi-mount protection
checksums, block I/O validation, and mount state transitions in
fs/ext4/mmp.c:

- CRC32C generation and verification in ext4_mmp_csum_set() and
  ext4_mmp_csum_verify() with and without METADATA_CSUM
- Block read/write error handling, magic validation (-EFSCORRUPTED),
  CRC mismatch handling (-EFSBADCRC), and buffer cleanup in
  read_mmp_block() and write_mmp_block()
- ext4_multi_mount_protect() validation of out-of-range MMP blocks,
  EXT4_MMP_SEQ_FSCK rejection (-EBUSY), EXT4_MMP_SEQ_CLEAN claim and
  ext4_stop_mmpd() teardown, active/racing node sequence conflicts,
  check_interval clamping, interrupted waits (-ETIMEDOUT), and kthread
  startup failure

Signed-off-by: Theodore Ts'o <tytso@mit.edu>
---
 fs/ext4/Makefile   |   2 +-
 fs/ext4/ext4.h     |  20 +-
 fs/ext4/mmp-test.c | 661 +++++++++++++++++++++++++++++++++++++++++++++
 fs/ext4/mmp.c      | 117 ++++++--
 4 files changed, 770 insertions(+), 30 deletions(-)
 create mode 100644 fs/ext4/mmp-test.c

diff --git a/fs/ext4/Makefile b/fs/ext4/Makefile
index 5cf735df7656..cb0d7b6edf6d 100644
--- a/fs/ext4/Makefile
+++ b/fs/ext4/Makefile
@@ -24,7 +24,7 @@ ext4-test-objs				+= inode-test.o mballoc-test.o \
 					   sysfs-test.o ioctl-test.o \
 					   resize-test.o migrate-test.o \
 					   indirect-test.o fsmap-test.o \
-					   move_extent-test.o
+					   move_extent-test.o mmp-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 8bc7269af580..aaf4722e709e 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -4207,9 +4207,25 @@ extern struct ext4_io_end_vec *ext4_last_io_end_vec(ext4_io_end_t *io_end);
 
 /* mmp.c */
 extern int ext4_multi_mount_protect(struct super_block *, ext4_fsblk_t);
-
-/* mmp.c */
 extern void ext4_stop_mmpd(struct ext4_sb_info *sbi);
+extern int ext4_mmp_submit_write(struct buffer_head *bh);
+extern int ext4_mmp_load_bh(struct super_block *sb, struct buffer_head **bh,
+			    ext4_fsblk_t mmp_block);
+extern long ext4_mmp_wait_timeout(long timeout);
+extern struct task_struct *ext4_mmp_run_thread(struct super_block *sb,
+					       const char *bdevname,
+					       int bdevname_len);
+#if IS_ENABLED(CONFIG_EXT4_KUNIT_TESTS)
+extern int ext4_mmp_csum_verify_test(struct super_block *sb,
+				     struct mmp_struct *mmp);
+extern void ext4_mmp_csum_set_test(struct super_block *sb,
+				   struct mmp_struct *mmp);
+extern int ext4_read_mmp_block_test(struct super_block *sb,
+				    struct buffer_head **bh,
+				    ext4_fsblk_t mmp_block);
+extern int ext4_write_mmp_block_test(struct super_block *sb,
+				     struct buffer_head *bh);
+#endif
 
 /* verity.c */
 extern const struct fsverity_operations ext4_verityops;
diff --git a/fs/ext4/mmp-test.c b/fs/ext4/mmp-test.c
new file mode 100644
index 000000000000..008857631394
--- /dev/null
+++ b/fs/ext4/mmp-test.c
@@ -0,0 +1,661 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * KUnit tests for fs/ext4/mmp.c
+ */
+
+#include <kunit/static_stub.h>
+#include <kunit/test.h>
+#include <linux/blkdev.h>
+#include <linux/buffer_head.h>
+#include <linux/fs.h>
+#include <linux/fs_context.h>
+#include <linux/kthread.h>
+#include <linux/module.h>
+#include <linux/part_stat.h>
+#include <linux/percpu.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/workqueue.h>
+
+#include "ext4.h"
+
+#define MMP_TEST_BLK_SIZE	4096U
+#define MMP_TEST_BLK_BITS	12U
+#define MMP_TEST_FIRST_DATA_BLK	1ULL
+#define MMP_TEST_TOTAL_BLKS	1024ULL
+#define MMP_TEST_VALID_BLK	10ULL
+#define MMP_MAX_WAITS		8U
+
+struct mmp_test_ctx {
+	struct super_block *sb;
+	struct ext4_sb_info sbi;
+	struct ext4_super_block *es;
+	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[MMP_TEST_BLK_SIZE];
+
+	/* Simulated on-disk MMP block and in-memory buffer_head */
+	u8 disk_buf[MMP_TEST_BLK_SIZE];
+	struct buffer_head mmp_bh;
+	u8 bh_buf[MMP_TEST_BLK_SIZE];
+
+	/* Deterministic I/O, sleep, and kthread fault injection */
+	int load_nomem;
+	int load_err_on_call;
+	unsigned int load_calls;
+	ext4_fsblk_t last_load_blk;
+
+	int write_err_on_call;
+	unsigned int write_calls;
+
+	unsigned int wait_calls;
+	long wait_timeouts[MMP_MAX_WAITS];
+	long wait_ret_on_call;
+	unsigned int wait_ret_target_call;
+	unsigned int mutate_seq_on_wait_call;
+	u32 mutated_seq;
+
+	int run_thread_err;
+	unsigned int run_thread_calls;
+};
+
+static struct kunit *active_mmp_test;
+
+static void mmp_noop_work(struct work_struct *work)
+{
+}
+
+static void mmp_noop_timer(struct timer_list *t)
+{
+}
+
+static const struct super_operations mmp_test_sops = {};
+
+static int mmp_init_fs_context(struct fs_context *fc)
+{
+	return 0;
+}
+
+static int mmp_set_super(struct super_block *sb, struct fs_context *fc)
+{
+	return set_anon_super_fc(sb, fc);
+}
+
+static struct file_system_type mmp_test_fs_type = {
+	.name			= "ext4_mmp_test",
+	.init_fs_context	= mmp_init_fs_context,
+	.kill_sb		= kill_anon_super,
+};
+
+static int mmp_dummy_kthread_fn(void *data)
+{
+	while (!kthread_should_stop()) {
+		set_current_state(TASK_INTERRUPTIBLE);
+		if (!kthread_should_stop())
+			schedule();
+	}
+	__set_current_state(TASK_RUNNING);
+	return 0;
+}
+
+static int mmp_submit_write_stub(struct buffer_head *bh)
+{
+	struct kunit *test = active_mmp_test;
+	struct mmp_test_ctx *ctx;
+
+	if (!test)
+		return -EIO;
+	ctx = test->priv;
+	ctx->write_calls++;
+	if (ctx->write_err_on_call &&
+	    ctx->write_calls == (unsigned int)ctx->write_err_on_call) {
+		clear_buffer_uptodate(bh);
+		return -EIO;
+	}
+
+	memcpy(ctx->disk_buf, bh->b_data, MMP_TEST_BLK_SIZE);
+	set_buffer_uptodate(bh);
+	return 0;
+}
+
+static int mmp_load_bh_stub(struct super_block *sb, struct buffer_head **bh,
+			    ext4_fsblk_t mmp_block)
+{
+	struct kunit *test = active_mmp_test;
+	struct mmp_test_ctx *ctx;
+
+	if (!test)
+		return -EIO;
+	ctx = test->priv;
+	ctx->load_calls++;
+	ctx->last_load_blk = mmp_block;
+
+	if (ctx->load_nomem && !*bh)
+		return -ENOMEM;
+
+	if (!*bh) {
+		*bh = &ctx->mmp_bh;
+		get_bh(*bh);
+	}
+
+	if (ctx->load_err_on_call &&
+	    ctx->load_calls == (unsigned int)ctx->load_err_on_call)
+		return -EIO;
+
+	memcpy((*bh)->b_data, ctx->disk_buf, MMP_TEST_BLK_SIZE);
+	set_buffer_uptodate(*bh);
+	return 0;
+}
+
+static long mmp_wait_timeout_stub(long timeout)
+{
+	struct kunit *test = active_mmp_test;
+	struct mmp_test_ctx *ctx;
+
+	if (!test)
+		return 1;
+	ctx = test->priv;
+	if (ctx->wait_calls < MMP_MAX_WAITS)
+		ctx->wait_timeouts[ctx->wait_calls] = timeout;
+	ctx->wait_calls++;
+
+	if (ctx->mutate_seq_on_wait_call &&
+	    ctx->wait_calls == ctx->mutate_seq_on_wait_call) {
+		struct mmp_struct *disk_mmp = (struct mmp_struct *)ctx->disk_buf;
+
+		disk_mmp->mmp_seq = cpu_to_le32(ctx->mutated_seq);
+		strscpy(disk_mmp->mmp_nodename, "other-node",
+			sizeof(disk_mmp->mmp_nodename));
+		ext4_mmp_csum_set_test(ctx->sb, disk_mmp);
+	}
+
+	if (ctx->wait_ret_target_call &&
+	    ctx->wait_calls == ctx->wait_ret_target_call)
+		return ctx->wait_ret_on_call;
+
+	return 0;
+}
+
+static struct task_struct *mmp_run_thread_stub(struct super_block *sb,
+					       const char *bdevname,
+					       int bdevname_len)
+{
+	struct kunit *test = active_mmp_test;
+	struct mmp_test_ctx *ctx;
+
+	if (!test)
+		return ERR_PTR(-EINVAL);
+	ctx = test->priv;
+	ctx->run_thread_calls++;
+	if (ctx->run_thread_err)
+		return ERR_PTR(ctx->run_thread_err);
+
+	return kthread_run(mmp_dummy_kthread_fn, NULL, "kunit-mmpd");
+}
+
+static void mmp_init_disk_block(struct mmp_test_ctx *ctx, u32 seq,
+				u16 check_interval)
+{
+	struct mmp_struct *mmp = (struct mmp_struct *)ctx->disk_buf;
+
+	memset(ctx->disk_buf, 0, sizeof(ctx->disk_buf));
+	mmp->mmp_magic = cpu_to_le32(EXT4_MMP_MAGIC);
+	mmp->mmp_seq = cpu_to_le32(seq);
+	mmp->mmp_time = cpu_to_le64(1700000000ULL);
+	strscpy(mmp->mmp_nodename, "init-node", sizeof(mmp->mmp_nodename));
+	strscpy(mmp->mmp_bdevname, "init-bdev", sizeof(mmp->mmp_bdevname));
+	mmp->mmp_check_interval = cpu_to_le16(check_interval);
+	ext4_mmp_csum_set_test(ctx->sb, mmp);
+}
+
+static void mmp_reset_counters(struct mmp_test_ctx *ctx)
+{
+	ctx->load_nomem = 0;
+	ctx->load_err_on_call = 0;
+	ctx->load_calls = 0;
+	ctx->last_load_blk = 0;
+	ctx->write_err_on_call = 0;
+	ctx->write_calls = 0;
+	ctx->wait_calls = 0;
+	memset(ctx->wait_timeouts, 0, sizeof(ctx->wait_timeouts));
+	ctx->wait_ret_on_call = 0;
+	ctx->wait_ret_target_call = 0;
+	ctx->mutate_seq_on_wait_call = 0;
+	ctx->mutated_seq = 0;
+	ctx->run_thread_err = 0;
+	ctx->run_thread_calls = 0;
+}
+
+static void mmp_cleanup_ctx(void *data)
+{
+	struct mmp_test_ctx *ctx = data;
+
+	active_mmp_test = NULL;
+
+	if (ctx->sbi.s_mmp_tsk)
+		ext4_stop_mmpd(&ctx->sbi);
+	ctx->sbi.s_mmp_bh = NULL;
+
+	cancel_work_sync(&ctx->sbi.s_sb_upd_work);
+	timer_delete_sync(&ctx->sbi.s_err_report);
+
+	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 mmp_test_ctx *mmp_setup_ctx(struct kunit *test)
+{
+	struct mmp_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);
+
+	fc = fs_context_for_mount(&mmp_test_fs_type, 0);
+	KUNIT_ASSERT_FALSE(test, IS_ERR(fc));
+	sb = sget_fc(fc, NULL, mmp_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, mmp_noop_work);
+	timer_setup(&ctx->sbi.s_err_report, mmp_noop_timer, 0);
+
+	err = kunit_add_action_or_reset(test, mmp_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);
+	strscpy(ctx->bdev_disk.disk_name, "mmp_bdev",
+		sizeof(ctx->bdev_disk.disk_name));
+	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;
+
+	sb->s_bdev = &ctx->bdev;
+	sb->s_blocksize = MMP_TEST_BLK_SIZE;
+	sb->s_blocksize_bits = MMP_TEST_BLK_BITS;
+	sb->s_magic = EXT4_SUPER_MAGIC;
+	sb->s_op = &mmp_test_sops;
+	sb->s_fs_info = &ctx->sbi;
+	strscpy(sb->s_id, "ext4_mmp_kunit", sizeof(sb->s_id));
+
+	ctx->sbh.b_data = ctx->sbh_data;
+	ctx->sbh.b_size = MMP_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->mmp_bh.b_data = ctx->bh_buf;
+	ctx->mmp_bh.b_size = MMP_TEST_BLK_SIZE;
+	ctx->mmp_bh.b_blocknr = MMP_TEST_VALID_BLK;
+	ctx->mmp_bh.b_bdev = &ctx->bdev;
+	atomic_set(&ctx->mmp_bh.b_count, 2);
+	set_buffer_mapped(&ctx->mmp_bh);
+
+	ctx->es->s_magic = cpu_to_le16(EXT4_SUPER_MAGIC);
+	ctx->es->s_first_data_block = cpu_to_le32(MMP_TEST_FIRST_DATA_BLK);
+	ctx->es->s_mmp_block = cpu_to_le64(MMP_TEST_VALID_BLK);
+	ctx->es->s_mmp_update_interval = cpu_to_le16(EXT4_MMP_MIN_CHECK_INTERVAL);
+	ctx->es->s_feature_incompat = cpu_to_le32(EXT4_FEATURE_INCOMPAT_MMP);
+	ctx->es->s_feature_ro_compat =
+		cpu_to_le32(EXT4_FEATURE_RO_COMPAT_METADATA_CSUM);
+	ext4_blocks_count_set(ctx->es, MMP_TEST_TOTAL_BLKS);
+
+	ctx->sbi.s_sb = sb;
+	ctx->sbi.s_es = ctx->es;
+	ctx->sbi.s_sbh = &ctx->sbh;
+	ctx->sbi.s_csum_seed = 0x9abcdef0U;
+	set_opt(sb, ERRORS_CONT);
+	spin_lock_init(&ctx->sbi.s_bdev_wb_lock);
+	spin_lock_init(&ctx->sbi.s_error_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);
+
+	test->priv = ctx;
+	active_mmp_test = test;
+
+	kunit_activate_static_stub(test, ext4_mmp_submit_write,
+				   mmp_submit_write_stub);
+	kunit_activate_static_stub(test, ext4_mmp_load_bh,
+				   mmp_load_bh_stub);
+	kunit_activate_static_stub(test, ext4_mmp_wait_timeout,
+				   mmp_wait_timeout_stub);
+	kunit_activate_static_stub(test, ext4_mmp_run_thread,
+				   mmp_run_thread_stub);
+
+	return ctx;
+}
+
+static void test_mmp_csum_set_and_verify(struct kunit *test)
+{
+	struct mmp_test_ctx *ctx = mmp_setup_ctx(test);
+	struct mmp_struct *mmp;
+	__le32 orig_csum;
+
+	mmp = kunit_kzalloc(test, sizeof(*mmp), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, mmp);
+
+	mmp->mmp_magic = cpu_to_le32(EXT4_MMP_MAGIC);
+	mmp->mmp_seq = cpu_to_le32(EXT4_MMP_SEQ_CLEAN);
+	strscpy(mmp->mmp_nodename, "node-a", sizeof(mmp->mmp_nodename));
+
+	/* Without METADATA_CSUM, set is a no-op and verify always returns 1 */
+	ext4_clear_feature_metadata_csum(ctx->sb);
+	mmp->mmp_checksum = cpu_to_le32(0xdeadbeefU);
+	ext4_mmp_csum_set_test(ctx->sb, mmp);
+	KUNIT_EXPECT_EQ(test, le32_to_cpu(mmp->mmp_checksum), 0xdeadbeefU);
+	KUNIT_EXPECT_EQ(test, ext4_mmp_csum_verify_test(ctx->sb, mmp), 1);
+
+	/* With METADATA_CSUM, set computes CRC32C and verify detects tampering */
+	ext4_set_feature_metadata_csum(ctx->sb);
+	mmp->mmp_checksum = 0;
+	ext4_mmp_csum_set_test(ctx->sb, mmp);
+	orig_csum = mmp->mmp_checksum;
+	KUNIT_EXPECT_NE(test, le32_to_cpu(orig_csum), 0U);
+	KUNIT_EXPECT_EQ(test, ext4_mmp_csum_verify_test(ctx->sb, mmp), 1);
+
+	/* Flipping payload field or csum seed invalidates verification */
+	mmp->mmp_seq = cpu_to_le32(7);
+	KUNIT_EXPECT_EQ(test, ext4_mmp_csum_verify_test(ctx->sb, mmp), 0);
+	mmp->mmp_seq = cpu_to_le32(EXT4_MMP_SEQ_CLEAN);
+	KUNIT_EXPECT_EQ(test, ext4_mmp_csum_verify_test(ctx->sb, mmp), 1);
+
+	ctx->sbi.s_csum_seed ^= 1U;
+	KUNIT_EXPECT_EQ(test, ext4_mmp_csum_verify_test(ctx->sb, mmp), 0);
+}
+
+static void test_read_and_write_mmp_block(struct kunit *test)
+{
+	struct mmp_test_ctx *ctx = mmp_setup_ctx(test);
+	struct buffer_head *bh = NULL;
+	struct mmp_struct *mmp;
+	int base_ref;
+	int ret;
+
+	/* Write populates checksum and persists block into disk_buf */
+	mmp = (struct mmp_struct *)ctx->mmp_bh.b_data;
+	memset(ctx->mmp_bh.b_data, 0, MMP_TEST_BLK_SIZE);
+	mmp->mmp_magic = cpu_to_le32(EXT4_MMP_MAGIC);
+	mmp->mmp_seq = cpu_to_le32(19);
+	ret = ext4_write_mmp_block_test(ctx->sb, &ctx->mmp_bh);
+	KUNIT_EXPECT_EQ(test, ret, 0);
+	KUNIT_EXPECT_EQ(test, ctx->write_calls, 1U);
+	KUNIT_EXPECT_EQ(test,
+			ext4_mmp_csum_verify_test(ctx->sb,
+				(struct mmp_struct *)ctx->disk_buf), 1);
+
+	/* Simulated write I/O error returns -EIO */
+	ctx->write_err_on_call = 2;
+	KUNIT_EXPECT_EQ(test, ext4_write_mmp_block_test(ctx->sb, &ctx->mmp_bh),
+			-EIO);
+	ctx->write_err_on_call = 0;
+
+	/* Valid read allocates/populates bh and verifies magic + checksum */
+	base_ref = atomic_read(&ctx->mmp_bh.b_count);
+	ret = ext4_read_mmp_block_test(ctx->sb, &bh, MMP_TEST_VALID_BLK);
+	KUNIT_EXPECT_EQ(test, ret, 0);
+	KUNIT_ASSERT_PTR_EQ(test, bh, &ctx->mmp_bh);
+	KUNIT_EXPECT_EQ(test, atomic_read(&ctx->mmp_bh.b_count), base_ref + 1);
+
+	/* Re-reading with existing bh refreshes contents without leaking ref */
+	((struct mmp_struct *)ctx->disk_buf)->mmp_seq = cpu_to_le32(23);
+	ext4_mmp_csum_set_test(ctx->sb, (struct mmp_struct *)ctx->disk_buf);
+	ret = ext4_read_mmp_block_test(ctx->sb, &bh, MMP_TEST_VALID_BLK);
+	KUNIT_EXPECT_EQ(test, ret, 0);
+	KUNIT_EXPECT_EQ(test,
+			le32_to_cpu(((struct mmp_struct *)bh->b_data)->mmp_seq),
+			23U);
+	KUNIT_EXPECT_EQ(test, atomic_read(&ctx->mmp_bh.b_count), base_ref + 1);
+	brelse(bh);
+	bh = NULL;
+
+	/* -ENOMEM when bh allocation fails */
+	ctx->load_nomem = 1;
+	KUNIT_EXPECT_EQ(test,
+			ext4_read_mmp_block_test(ctx->sb, &bh,
+						 MMP_TEST_VALID_BLK),
+			-ENOMEM);
+	KUNIT_EXPECT_NULL(test, bh);
+	ctx->load_nomem = 0;
+
+	/* -EIO read failure releases bh and resets *bh to NULL */
+	mmp_reset_counters(ctx);
+	ctx->load_err_on_call = 1;
+	KUNIT_EXPECT_EQ(test,
+			ext4_read_mmp_block_test(ctx->sb, &bh,
+						 MMP_TEST_VALID_BLK),
+			-EIO);
+	KUNIT_EXPECT_NULL(test, bh);
+	KUNIT_EXPECT_EQ(test, atomic_read(&ctx->mmp_bh.b_count), base_ref);
+
+	/* Bad magic returns -EFSCORRUPTED and cleans up bh */
+	mmp_reset_counters(ctx);
+	((struct mmp_struct *)ctx->disk_buf)->mmp_magic = cpu_to_le32(0x1234U);
+	KUNIT_EXPECT_EQ(test,
+			ext4_read_mmp_block_test(ctx->sb, &bh,
+						 MMP_TEST_VALID_BLK),
+			-EFSCORRUPTED);
+	KUNIT_EXPECT_NULL(test, bh);
+	KUNIT_EXPECT_EQ(test, atomic_read(&ctx->mmp_bh.b_count), base_ref);
+
+	/* Bad checksum returns -EFSBADCRC and cleans up bh */
+	mmp_init_disk_block(ctx, EXT4_MMP_SEQ_CLEAN, 5);
+	((struct mmp_struct *)ctx->disk_buf)->mmp_checksum ^= cpu_to_le32(1U);
+	KUNIT_EXPECT_EQ(test,
+			ext4_read_mmp_block_test(ctx->sb, &bh,
+						 MMP_TEST_VALID_BLK),
+			-EFSBADCRC);
+	KUNIT_EXPECT_NULL(test, bh);
+	KUNIT_EXPECT_EQ(test, atomic_read(&ctx->mmp_bh.b_count), base_ref);
+}
+
+static void test_mmp_invalid_block_and_io_errors(struct kunit *test)
+{
+	struct mmp_test_ctx *ctx = mmp_setup_ctx(test);
+
+	/* Block below s_first_data_block (1) or >= blocks_count (1024) */
+	KUNIT_EXPECT_EQ(test, ext4_multi_mount_protect(ctx->sb, 0), -EINVAL);
+	KUNIT_EXPECT_EQ(test,
+			ext4_multi_mount_protect(ctx->sb, MMP_TEST_TOTAL_BLKS),
+			-EINVAL);
+	KUNIT_EXPECT_EQ(test, ctx->load_calls, 0U);
+
+	/* Initial read failure propagates error */
+	mmp_init_disk_block(ctx, EXT4_MMP_SEQ_CLEAN, 5);
+	mmp_reset_counters(ctx);
+	ctx->load_err_on_call = 1;
+	KUNIT_EXPECT_EQ(test,
+			ext4_multi_mount_protect(ctx->sb, MMP_TEST_VALID_BLK),
+			-EIO);
+
+	/* Write failure when claiming MMP block */
+	mmp_reset_counters(ctx);
+	ctx->write_err_on_call = 1;
+	KUNIT_EXPECT_EQ(test,
+			ext4_multi_mount_protect(ctx->sb, MMP_TEST_VALID_BLK),
+			-EIO);
+
+	/* Post-write verification read failure */
+	mmp_init_disk_block(ctx, EXT4_MMP_SEQ_CLEAN, 5);
+	mmp_reset_counters(ctx);
+	ctx->load_err_on_call = 2;
+	KUNIT_EXPECT_EQ(test,
+			ext4_multi_mount_protect(ctx->sb, MMP_TEST_VALID_BLK),
+			-EIO);
+}
+
+static void test_mmp_clean_and_fsck_sequences(struct kunit *test)
+{
+	struct mmp_test_ctx *ctx = mmp_setup_ctx(test);
+	struct mmp_struct *disk_mmp = (struct mmp_struct *)ctx->disk_buf;
+	int base_ref;
+	int ret;
+
+	/* EXT4_MMP_SEQ_FSCK fails immediately with -EBUSY without sleeping */
+	mmp_init_disk_block(ctx, EXT4_MMP_SEQ_FSCK, 5);
+	mmp_reset_counters(ctx);
+	ret = ext4_multi_mount_protect(ctx->sb, MMP_TEST_VALID_BLK);
+	KUNIT_EXPECT_EQ(test, ret, -EBUSY);
+	KUNIT_EXPECT_EQ(test, ctx->wait_calls, 0U);
+	KUNIT_EXPECT_EQ(test, ctx->write_calls, 0U);
+
+	/*
+	 * EXT4_MMP_SEQ_CLEAN skips pre-claim wait, writes a valid sequence
+	 * <= EXT4_MMP_SEQ_MAX, starts kmmpd, and ext4_stop_mmpd() cleans up.
+	 */
+	mmp_init_disk_block(ctx, EXT4_MMP_SEQ_CLEAN, 5);
+	mmp_reset_counters(ctx);
+	base_ref = atomic_read(&ctx->mmp_bh.b_count);
+
+	ret = ext4_multi_mount_protect(ctx->sb, MMP_TEST_VALID_BLK);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	KUNIT_EXPECT_EQ(test, ctx->wait_calls, 1U);
+	KUNIT_EXPECT_EQ(test, ctx->write_calls, 1U);
+	KUNIT_EXPECT_EQ(test, ctx->run_thread_calls, 1U);
+	KUNIT_EXPECT_LE(test, le32_to_cpu(disk_mmp->mmp_seq), EXT4_MMP_SEQ_MAX);
+	KUNIT_EXPECT_NOT_NULL(test, ctx->sbi.s_mmp_tsk);
+	KUNIT_EXPECT_PTR_EQ(test, ctx->sbi.s_mmp_bh, &ctx->mmp_bh);
+	KUNIT_EXPECT_STREQ(test,
+			   ((struct mmp_struct *)ctx->mmp_bh.b_data)->mmp_bdevname,
+			   "mmp_bdev");
+
+	ext4_stop_mmpd(&ctx->sbi);
+	KUNIT_EXPECT_NULL(test, ctx->sbi.s_mmp_tsk);
+	KUNIT_EXPECT_EQ(test, atomic_read(&ctx->mmp_bh.b_count), base_ref);
+	ctx->sbi.s_mmp_bh = NULL;
+}
+
+static void test_mmp_active_seq_conflict_and_claim(struct kunit *test)
+{
+	struct mmp_test_ctx *ctx = mmp_setup_ctx(test);
+	int ret;
+
+	/*
+	 * Case 1: Active sequence on disk; s_mmp_update_interval = 3 (floored
+	 * to 5), disk mmp_check_interval = 12 (overrides 5 -> wait_time = 25s).
+	 * Remote node updates mmp_seq during wait #1 -> fails with -EBUSY.
+	 */
+	ctx->es->s_mmp_update_interval = cpu_to_le16(3);
+	mmp_init_disk_block(ctx, 100, 12);
+	mmp_reset_counters(ctx);
+	ctx->mutate_seq_on_wait_call = 1;
+	ctx->mutated_seq = 101;
+
+	ret = ext4_multi_mount_protect(ctx->sb, MMP_TEST_VALID_BLK);
+	KUNIT_EXPECT_EQ(test, ret, -EBUSY);
+	KUNIT_EXPECT_EQ(test, ctx->wait_calls, 1U);
+	KUNIT_EXPECT_EQ(test, ctx->wait_timeouts[0], (long)(25 * HZ));
+	KUNIT_EXPECT_EQ(test, ctx->write_calls, 0U);
+
+	/*
+	 * Case 2: Stale sequence unchanged on wait #1, but racing mounter
+	 * overwrites mmp_seq during post-claim wait #2 -> fails with -EBUSY.
+	 */
+	mmp_init_disk_block(ctx, 100, 12);
+	mmp_reset_counters(ctx);
+	ctx->mutate_seq_on_wait_call = 2;
+	ctx->mutated_seq = 0x5555U;
+
+	ret = ext4_multi_mount_protect(ctx->sb, MMP_TEST_VALID_BLK);
+	KUNIT_EXPECT_EQ(test, ret, -EBUSY);
+	KUNIT_EXPECT_EQ(test, ctx->wait_calls, 2U);
+	KUNIT_EXPECT_EQ(test, ctx->wait_timeouts[0], (long)(25 * HZ));
+	KUNIT_EXPECT_EQ(test, ctx->wait_timeouts[1], (long)(25 * HZ));
+	KUNIT_EXPECT_EQ(test, ctx->write_calls, 1U);
+
+	/*
+	 * Case 3: Stale sequence with large check_interval = 80 (wait_time
+	 * capped to 80 + 60 = 140s via min(2*x+1, x+60)) succeeds cleanly.
+	 */
+	mmp_init_disk_block(ctx, 100, 80);
+	mmp_reset_counters(ctx);
+
+	ret = ext4_multi_mount_protect(ctx->sb, MMP_TEST_VALID_BLK);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	KUNIT_EXPECT_EQ(test, ctx->wait_calls, 2U);
+	KUNIT_EXPECT_EQ(test, ctx->wait_timeouts[0], (long)(140 * HZ));
+	KUNIT_EXPECT_EQ(test, ctx->wait_timeouts[1], (long)(140 * HZ));
+
+	ext4_stop_mmpd(&ctx->sbi);
+	ctx->sbi.s_mmp_bh = NULL;
+}
+
+static void test_mmp_interrupted_waits_and_kthread_failure(struct kunit *test)
+{
+	struct mmp_test_ctx *ctx = mmp_setup_ctx(test);
+
+	/* Signal interruption on pre-claim wait returns -ETIMEDOUT */
+	mmp_init_disk_block(ctx, 77, 5);
+	mmp_reset_counters(ctx);
+	ctx->wait_ret_target_call = 1;
+	ctx->wait_ret_on_call = 3 * HZ;
+	KUNIT_EXPECT_EQ(test,
+			ext4_multi_mount_protect(ctx->sb, MMP_TEST_VALID_BLK),
+			-ETIMEDOUT);
+
+	/* Signal interruption on post-claim wait returns -ETIMEDOUT */
+	mmp_init_disk_block(ctx, 77, 5);
+	mmp_reset_counters(ctx);
+	ctx->wait_ret_target_call = 2;
+	ctx->wait_ret_on_call = 2 * HZ;
+	KUNIT_EXPECT_EQ(test,
+			ext4_multi_mount_protect(ctx->sb, MMP_TEST_VALID_BLK),
+			-ETIMEDOUT);
+
+	/* kthread creation failure returns -ENOMEM and leaves s_mmp_tsk NULL */
+	mmp_init_disk_block(ctx, EXT4_MMP_SEQ_CLEAN, 5);
+	mmp_reset_counters(ctx);
+	ctx->run_thread_err = -ENOMEM;
+	KUNIT_EXPECT_EQ(test,
+			ext4_multi_mount_protect(ctx->sb, MMP_TEST_VALID_BLK),
+			-ENOMEM);
+	KUNIT_EXPECT_NULL(test, ctx->sbi.s_mmp_tsk);
+	ctx->sbi.s_mmp_bh = NULL;
+}
+
+static struct kunit_case mmp_test_cases[] = {
+	KUNIT_CASE(test_mmp_csum_set_and_verify),
+	KUNIT_CASE(test_read_and_write_mmp_block),
+	KUNIT_CASE(test_mmp_invalid_block_and_io_errors),
+	KUNIT_CASE(test_mmp_clean_and_fsck_sequences),
+	KUNIT_CASE(test_mmp_active_seq_conflict_and_claim),
+	KUNIT_CASE(test_mmp_interrupted_waits_and_kthread_failure),
+	{}
+};
+
+static struct kunit_suite mmp_test_suite = {
+	.name = "ext4_mmp_test",
+	.test_cases = mmp_test_cases,
+};
+
+kunit_test_suites(&mmp_test_suite);
+
+MODULE_DESCRIPTION("KUnit tests for ext4 multi-mount protection (MMP)");
+MODULE_LICENSE("GPL");
diff --git a/fs/ext4/mmp.c b/fs/ext4/mmp.c
index 7ce361484b38..025c9f332726 100644
--- a/fs/ext4/mmp.c
+++ b/fs/ext4/mmp.c
@@ -1,4 +1,5 @@
 // SPDX-License-Identifier: GPL-2.0
+#include <kunit/static_stub.h>
 #include <linux/fs.h>
 #include <linux/random.h>
 #include <linux/buffer_head.h>
@@ -35,6 +36,20 @@ static void ext4_mmp_csum_set(struct super_block *sb, struct mmp_struct *mmp)
 	mmp->mmp_checksum = ext4_mmp_csum(sb, mmp);
 }
 
+int ext4_mmp_submit_write(struct buffer_head *bh)
+{
+	KUNIT_STATIC_STUB_REDIRECT(ext4_mmp_submit_write, bh);
+
+	lock_buffer(bh);
+	bh_submit(bh, REQ_OP_WRITE | REQ_SYNC | REQ_META | REQ_PRIO,
+		  bh_end_write);
+	wait_on_buffer(bh);
+	if (unlikely(!buffer_uptodate(bh)))
+		return -EIO;
+	return 0;
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_mmp_submit_write);
+
 /*
  * Write the MMP block using REQ_SYNC to try to get the block on-disk
  * faster.
@@ -45,13 +60,7 @@ static int write_mmp_block_thawed(struct super_block *sb,
 	struct mmp_struct *mmp = (struct mmp_struct *)(bh->b_data);
 
 	ext4_mmp_csum_set(sb, mmp);
-	lock_buffer(bh);
-	bh_submit(bh, REQ_OP_WRITE | REQ_SYNC | REQ_META | REQ_PRIO,
-			bh_end_write);
-	wait_on_buffer(bh);
-	if (unlikely(!buffer_uptodate(bh)))
-		return -EIO;
-	return 0;
+	return ext4_mmp_submit_write(bh);
 }
 
 static int write_mmp_block(struct super_block *sb, struct buffer_head *bh)
@@ -64,6 +73,35 @@ static int write_mmp_block(struct super_block *sb, struct buffer_head *bh)
 		return write_mmp_block_thawed(sb, bh);
 }
 
+int ext4_mmp_load_bh(struct super_block *sb, struct buffer_head **bh,
+		     ext4_fsblk_t mmp_block)
+{
+	KUNIT_STATIC_STUB_REDIRECT(ext4_mmp_load_bh, sb, bh, mmp_block);
+
+	/*
+	 * This would be sb_bread(sb, mmp_block), except we need to be sure
+	 * that the MD RAID device cache has been bypassed, and that the read
+	 * is not blocked in the elevator.
+	 */
+	if (!*bh) {
+		*bh = sb_getblk(sb, mmp_block);
+		if (!*bh)
+			return -ENOMEM;
+	}
+
+	lock_buffer(*bh);
+	return ext4_read_bh(*bh, REQ_META | REQ_PRIO, NULL, false);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_mmp_load_bh);
+
+long ext4_mmp_wait_timeout(long timeout)
+{
+	KUNIT_STATIC_STUB_REDIRECT(ext4_mmp_wait_timeout, timeout);
+
+	return schedule_timeout_interruptible(timeout);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_mmp_wait_timeout);
+
 /*
  * Read the MMP block. It _must_ be read from disk and hence we clear the
  * uptodate flag on the buffer.
@@ -77,19 +115,7 @@ static int read_mmp_block(struct super_block *sb, struct buffer_head **bh,
 	if (*bh)
 		clear_buffer_uptodate(*bh);
 
-	/* This would be sb_bread(sb, mmp_block), except we need to be sure
-	 * that the MD RAID device cache has been bypassed, and that the read
-	 * is not blocked in the elevator. */
-	if (!*bh) {
-		*bh = sb_getblk(sb, mmp_block);
-		if (!*bh) {
-			ret = -ENOMEM;
-			goto warn_exit;
-		}
-	}
-
-	lock_buffer(*bh);
-	ret = ext4_read_bh(*bh, REQ_META | REQ_PRIO, NULL, false);
+	ret = ext4_mmp_load_bh(sb, bh, mmp_block);
 	if (ret)
 		goto warn_exit;
 
@@ -185,8 +211,7 @@ static int kmmpd(void *data)
 
 		diff = jiffies - last_update_time;
 		if (diff < mmp_update_interval * HZ)
-			schedule_timeout_interruptible(mmp_update_interval *
-						       HZ - diff);
+			ext4_mmp_wait_timeout(mmp_update_interval * HZ - diff);
 
 		/*
 		 * We need to make sure that more than mmp_check_interval
@@ -250,6 +275,16 @@ static int kmmpd(void *data)
 	return retval;
 }
 
+struct task_struct *ext4_mmp_run_thread(struct super_block *sb,
+					const char *bdevname, int bdevname_len)
+{
+	KUNIT_STATIC_STUB_REDIRECT(ext4_mmp_run_thread, sb, bdevname,
+				   bdevname_len);
+
+	return kthread_run(kmmpd, sb, "kmmpd-%.*s", bdevname_len, bdevname);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_mmp_run_thread);
+
 void ext4_stop_mmpd(struct ext4_sb_info *sbi)
 {
 	if (sbi->s_mmp_tsk) {
@@ -258,6 +293,7 @@ void ext4_stop_mmpd(struct ext4_sb_info *sbi)
 		sbi->s_mmp_tsk = NULL;
 	}
 }
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_stop_mmpd);
 
 /*
  * Get a random new sequence number but make sure it is not greater than
@@ -323,7 +359,7 @@ int ext4_multi_mount_protect(struct super_block *sb,
 		ext4_warning(sb, "MMP interval %u higher than expected, please"
 			     " wait.\n", wait_time * 2);
 
-	if (schedule_timeout_interruptible(HZ * wait_time) != 0) {
+	if (ext4_mmp_wait_timeout(HZ * wait_time) != 0) {
 		ext4_warning(sb, "MMP startup interrupted, failing mount\n");
 		retval = -ETIMEDOUT;
 		goto failed;
@@ -358,7 +394,7 @@ int ext4_multi_mount_protect(struct super_block *sb,
 	/*
 	 * wait for MMP interval and check mmp_seq.
 	 */
-	if (schedule_timeout_interruptible(HZ * wait_time) != 0) {
+	if (ext4_mmp_wait_timeout(HZ * wait_time) != 0) {
 		ext4_warning(sb, "MMP startup interrupted, failing mount");
 		retval = -ETIMEDOUT;
 		goto failed;
@@ -384,9 +420,8 @@ int ext4_multi_mount_protect(struct super_block *sb,
 	/*
 	 * Start a kernel thread to update the MMP block periodically.
 	 */
-	EXT4_SB(sb)->s_mmp_tsk = kthread_run(kmmpd, sb, "kmmpd-%.*s",
-					     (int)sizeof(mmp->mmp_bdevname),
-					     mmp->mmp_bdevname);
+	EXT4_SB(sb)->s_mmp_tsk = ext4_mmp_run_thread(sb, mmp->mmp_bdevname,
+					     (int)sizeof(mmp->mmp_bdevname));
 	if (IS_ERR(EXT4_SB(sb)->s_mmp_tsk)) {
 		EXT4_SB(sb)->s_mmp_tsk = NULL;
 		ext4_warning(sb, "Unable to create kmmpd thread for %s.",
@@ -401,3 +436,31 @@ int ext4_multi_mount_protect(struct super_block *sb,
 	brelse(bh);
 	return retval;
 }
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_multi_mount_protect);
+
+#if IS_ENABLED(CONFIG_EXT4_KUNIT_TESTS)
+int ext4_mmp_csum_verify_test(struct super_block *sb, struct mmp_struct *mmp)
+{
+	return ext4_mmp_csum_verify(sb, mmp);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_mmp_csum_verify_test);
+
+void ext4_mmp_csum_set_test(struct super_block *sb, struct mmp_struct *mmp)
+{
+	ext4_mmp_csum_set(sb, mmp);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_mmp_csum_set_test);
+
+int ext4_read_mmp_block_test(struct super_block *sb, struct buffer_head **bh,
+			     ext4_fsblk_t mmp_block)
+{
+	return read_mmp_block(sb, bh, mmp_block);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_read_mmp_block_test);
+
+int ext4_write_mmp_block_test(struct super_block *sb, struct buffer_head *bh)
+{
+	return write_mmp_block(sb, bh);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_write_mmp_block_test);
+#endif
-- 
2.56.0.542.g3ef89b7020-goog


  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 ` [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 ` Theodore Ts'o [this message]
2026-11-12  4:29   ` [PATCH 26/27] ext4: add KUnit tests for fs/ext4/mmp.c 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-27-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