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 20/27] ext4: add KUnit tests for fs/ext4/resize.c
Date: Wed, 11 Nov 2026 23:06:31 -0500 [thread overview]
Message-ID: <20261112040638.1576836-21-tytso@mit.edu> (raw)
In-Reply-To: <20261112040638.1576836-1-tytso@mit.edu>
Add a KUnit test suite (ext4_resize_test) covering online filesystem
resizing, group validation, flex_bg table packing, backup descriptor
reservation, and meta_bg conversion in fs/ext4/resize.c:
- Precondition checks in ext4_resize_begin() and EXT4_FLAGS_RESIZING
mutual exclusion with ext4_resize_end().
- Backup group iteration in ext4_list_backups() (sparse_super powers of
3/5/7, dense non-sparse sequence, and sparse_super2 s_backup_bgs) and
reserved GDT validation in verify_reserved_gdb().
- Group geometry and overlap validation in verify_group_input() across
partial prior groups, reserved-block ratios, out-of-bounds bitmaps and
inode tables, bitmap collisions, and superblock/GDT overhead overlaps.
- Flex group batch sizing in alloc_flex_gd(), descriptor flag and
partial-group initialization in ext4_setup_next_flex_gd(), and packed
metadata table placement and -ENOSPC handling in
ext4_alloc_group_tables().
- Online partial-last-group extension in ext4_group_extend() updating
superblock counts, block bitmaps, buddy caches, and backup
superblocks.
- Group addition in ext4_group_add() including backup reserved GDT
updates via reserve_backup_gdb(), resize_inode validation and meta_bg
conversion in ext4_convert_meta_bg(), and end-to-end ext4_resize_fs()
growth across reserved GDT consumption into meta_bg.
Route raw sb_getblk() calls in resize.c through ext4_sb_getblk() and
wrap sb_issue_zeroout() in ext4_resize_issue_zeroout() with
KUNIT_STATIC_STUB_REDIRECT so resize metadata I/O can be exercised
deterministically in memory.
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
---
fs/ext4/Makefile | 3 +-
fs/ext4/ext4.h | 32 +
fs/ext4/resize-test.c | 1341 +++++++++++++++++++++++++++++++++++++++++
fs/ext4/resize.c | 102 +++-
4 files changed, 1453 insertions(+), 25 deletions(-)
create mode 100644 fs/ext4/resize-test.c
diff --git a/fs/ext4/Makefile b/fs/ext4/Makefile
index c18530508863..6163d47f3627 100644
--- a/fs/ext4/Makefile
+++ b/fs/ext4/Makefile
@@ -21,7 +21,8 @@ ext4-test-objs += inode-test.o mballoc-test.o \
fast_commit-test.o ialloc-test.o \
balloc-test.o orphan-test.o \
block_validity-test.o super-test.o \
- sysfs-test.o ioctl-test.o
+ sysfs-test.o ioctl-test.o \
+ resize-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 842fba63f404..3a735c69e2e3 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -688,6 +688,17 @@ enum {
GROUP_TABLE_COUNT,
};
+/*
+ * ext4_new_flex_group_data is used by 64bit-resize interface to add a flex
+ * group each time.
+ */
+struct ext4_new_flex_group_data {
+ struct ext4_new_group_data *groups;
+ __u16 *bg_flags;
+ ext4_group_t resize_bg;
+ ext4_group_t count;
+};
+
/*
* Flags used by ext4_map_blocks()
*/
@@ -3399,6 +3410,8 @@ extern bool ext4_empty_dir(struct inode *inode);
/* resize.c */
extern void ext4_kvfree_array_rcu(void *to_free);
+extern int ext4_resize_issue_zeroout(struct super_block *sb, sector_t blk,
+ sector_t nr_blks, gfp_t gfp_mask);
extern int ext4_group_add(struct super_block *sb,
struct ext4_new_group_data *input);
extern int ext4_group_extend(struct super_block *sb,
@@ -3408,6 +3421,25 @@ extern int ext4_resize_fs(struct super_block *sb, ext4_fsblk_t n_blocks_count);
extern unsigned int ext4_list_backups(struct super_block *sb,
unsigned int *three, unsigned int *five,
unsigned int *seven);
+#if IS_ENABLED(CONFIG_EXT4_KUNIT_TESTS)
+extern int ext4_verify_group_input_test(struct super_block *sb,
+ struct ext4_new_group_data *input);
+extern struct ext4_new_flex_group_data *
+ext4_alloc_flex_gd_test(unsigned int flexbg_size, ext4_group_t o_group,
+ ext4_group_t n_group);
+extern void ext4_free_flex_gd_test(struct ext4_new_flex_group_data *flex_gd);
+extern int ext4_alloc_group_tables_test(struct super_block *sb,
+ struct ext4_new_flex_group_data *flex_gd,
+ unsigned int flexbg_size);
+extern int ext4_verify_reserved_gdb_test(struct super_block *sb,
+ ext4_group_t end,
+ struct buffer_head *primary);
+extern int ext4_setup_next_flex_gd_test(struct super_block *sb,
+ struct ext4_new_flex_group_data *flex_gd,
+ ext4_fsblk_t n_blocks_count);
+extern int ext4_convert_meta_bg_test(struct super_block *sb,
+ struct inode *inode);
+#endif
/* super.c */
extern struct buffer_head *ext4_sb_bread(struct super_block *sb,
diff --git a/fs/ext4/resize-test.c b/fs/ext4/resize-test.c
new file mode 100644
index 000000000000..081a5d6c6fd9
--- /dev/null
+++ b/fs/ext4/resize-test.c
@@ -0,0 +1,1341 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * KUnit tests for fs/ext4/resize.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/fscrypt.h>
+#include <linux/iversion.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/part_stat.h>
+#include <linux/percpu.h>
+#include <linux/percpu_counter.h>
+#include <linux/quotaops.h>
+#include <linux/rcupdate.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/uuid.h>
+#include <linux/workqueue.h>
+
+#include "ext4.h"
+#include "ext4_jbd2.h"
+#include "mballoc.h"
+
+#define RESIZE_TEST_BLK_SIZE 1024U
+#define RESIZE_TEST_BLK_BITS 10U
+#define RESIZE_TEST_BLOCKS_PER_GRP 1024U
+#define RESIZE_TEST_FIRST_DATA_BLK 1U
+#define RESIZE_TEST_INODE_SIZE 256U
+#define RESIZE_TEST_EXTRA_ISIZE 32U
+#define RESIZE_TEST_INODES_PER_GRP 32U
+#define RESIZE_TEST_ITB_PER_GRP 8U
+#define RESIZE_TEST_DESC_SIZE 64U
+#define RESIZE_TEST_DESC_PER_BLK 16U
+#define RESIZE_TEST_DESC_PER_BLK_BITS 4U
+
+struct resize_mock_blk {
+ struct list_head node;
+ ext4_fsblk_t blocknr;
+ struct buffer_head bh;
+ u8 *data;
+};
+
+struct resize_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 inode bd_inode;
+ struct folio *bh_folio;
+ struct list_head blks;
+ struct buffer_head iloc_bh;
+ u8 iloc_buf[RESIZE_TEST_BLK_SIZE];
+ struct inode *resize_inode;
+ ext4_fsblk_t device_blocks;
+ ext4_fsblk_t last_freed_blk;
+ unsigned long last_freed_count;
+ int zeroout_calls;
+ sector_t zeroout_blks;
+ int zeroout_err;
+ bool sb_getblk_fail;
+ bool mb_inited;
+ bool counters_inited;
+};
+
+static struct kunit *active_resize_test;
+
+static struct inode *resize_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 = RESIZE_TEST_EXTRA_ISIZE;
+ ei->i_last_alloc_group = ~0U;
+
+ return &ei->vfs_inode;
+}
+
+static void resize_test_free_inode(struct inode *inode)
+{
+ ext4_es_remove_extent(inode, 0, EXT_MAX_BLOCKS);
+ kfree(EXT4_I(inode));
+}
+
+static void resize_test_evict_inode(struct inode *inode)
+{
+ truncate_inode_pages_final(&inode->i_data);
+ clear_inode(inode);
+}
+
+#ifdef CONFIG_QUOTA
+static struct dquot __rcu **resize_test_get_dquots(struct inode *inode)
+{
+ return EXT4_I(inode)->i_dquot;
+}
+#endif
+
+static const struct super_operations resize_test_sops = {
+ .alloc_inode = resize_test_alloc_inode,
+ .free_inode = resize_test_free_inode,
+ .evict_inode = resize_test_evict_inode,
+#ifdef CONFIG_QUOTA
+ .get_dquots = resize_test_get_dquots,
+#endif
+};
+
+static int resize_test_init_fs_context(struct fs_context *fc)
+{
+ return 0;
+}
+
+static void resize_test_sb_upd_work(struct work_struct *work)
+{
+}
+
+static void resize_test_err_timer(struct timer_list *t)
+{
+}
+
+static struct file_system_type resize_test_fs_type = {
+ .name = "ext4_resize_kunit",
+ .init_fs_context = resize_test_init_fs_context,
+ .kill_sb = kill_anon_super,
+};
+
+static void resize_init_mock_bh(struct buffer_head *bh, struct folio *folio,
+ u8 *data, size_t size, sector_t blocknr)
+{
+ memset(bh, 0, sizeof(*bh));
+ bh->b_folio = folio;
+ bh->b_data = (char *)data;
+ bh->b_size = size;
+ bh->b_blocknr = blocknr;
+ atomic_set(&bh->b_count, 2);
+ set_buffer_uptodate(bh);
+}
+
+static struct buffer_head *resize_lookup_bh(struct resize_test_ctx *ctx,
+ ext4_fsblk_t blocknr)
+{
+ struct resize_mock_blk *blk;
+
+ list_for_each_entry(blk, &ctx->blks, node) {
+ if (blk->blocknr == blocknr)
+ return &blk->bh;
+ }
+ return NULL;
+}
+
+static struct buffer_head *resize_get_or_create_bh(struct resize_test_ctx *ctx,
+ ext4_fsblk_t blocknr)
+{
+ struct buffer_head *bh;
+ struct resize_mock_blk *blk;
+
+ bh = resize_lookup_bh(ctx, blocknr);
+ if (bh) {
+ get_bh(bh);
+ return bh;
+ }
+
+ blk = kzalloc_obj(*blk);
+ if (!blk)
+ return NULL;
+ blk->data = kzalloc(RESIZE_TEST_BLK_SIZE, GFP_KERNEL);
+ if (!blk->data) {
+ kfree(blk);
+ return NULL;
+ }
+ blk->blocknr = blocknr;
+ resize_init_mock_bh(&blk->bh, ctx->bh_folio, blk->data,
+ RESIZE_TEST_BLK_SIZE, blocknr);
+ list_add_tail(&blk->node, &ctx->blks);
+ get_bh(&blk->bh);
+ return &blk->bh;
+}
+
+static struct buffer_head *resize_sb_getblk_stub(struct super_block *sb,
+ ext4_fsblk_t block)
+{
+ struct kunit *test = active_resize_test;
+ struct resize_test_ctx *ctx;
+
+ if (!test)
+ return NULL;
+ ctx = test->priv;
+ if (!ctx || sb != ctx->sb || ctx->sb_getblk_fail)
+ return NULL;
+
+ return resize_get_or_create_bh(ctx, block);
+}
+
+static struct buffer_head *resize_sb_bread_stub(struct super_block *sb,
+ sector_t block,
+ blk_opf_t op_flags)
+{
+ struct kunit *test = active_resize_test;
+ struct resize_test_ctx *ctx;
+ struct buffer_head *bh;
+
+ if (!test)
+ return ERR_PTR(-EIO);
+ ctx = test->priv;
+ if (!ctx || sb != ctx->sb)
+ return ERR_PTR(-EIO);
+ if ((ext4_fsblk_t)block >= ctx->device_blocks)
+ return ERR_PTR(-EIO);
+
+ bh = resize_get_or_create_bh(ctx, block);
+ if (!bh)
+ return ERR_PTR(-ENOMEM);
+ return bh;
+}
+
+static int resize_zeroout_stub(struct super_block *sb, sector_t blk,
+ sector_t nr_blks, gfp_t gfp_mask)
+{
+ struct kunit *test = active_resize_test;
+ struct resize_test_ctx *ctx;
+ sector_t i;
+
+ if (!test)
+ return 0;
+ ctx = test->priv;
+ if (!ctx)
+ return 0;
+ if (ctx->zeroout_err)
+ return ctx->zeroout_err;
+
+ ctx->zeroout_calls++;
+ ctx->zeroout_blks += nr_blks;
+ for (i = 0; i < nr_blks; i++) {
+ struct buffer_head *bh = resize_lookup_bh(ctx, blk + i);
+
+ if (bh)
+ memset(bh->b_data, 0, bh->b_size);
+ }
+ return 0;
+}
+
+static struct inode *resize_iget_stub(struct super_block *sb, unsigned long ino,
+ ext4_iget_flags flags,
+ const char *function, unsigned int line)
+{
+ struct kunit *test = active_resize_test;
+ struct resize_test_ctx *ctx;
+
+ if (!test)
+ return ERR_PTR(-ENOENT);
+ ctx = test->priv;
+ if (!ctx || sb != ctx->sb)
+ return ERR_PTR(-ENOENT);
+ if (ino == EXT4_RESIZE_INO && ctx->resize_inode) {
+ ihold(ctx->resize_inode);
+ return ctx->resize_inode;
+ }
+ return ERR_PTR(-ENOENT);
+}
+
+static int resize_get_inode_loc_stub(struct inode *inode,
+ struct ext4_iloc *iloc)
+{
+ struct kunit *test = active_resize_test;
+ struct resize_test_ctx *ctx;
+
+ if (!test)
+ return -EIO;
+ ctx = test->priv;
+ if (!ctx)
+ return -EIO;
+
+ get_bh(&ctx->iloc_bh);
+ iloc->bh = &ctx->iloc_bh;
+ iloc->offset = 0;
+ iloc->block_group = 0;
+ return 0;
+}
+
+static int resize_mark_iloc_dirty_stub(handle_t *handle, struct inode *inode,
+ struct ext4_iloc *iloc)
+{
+ brelse(iloc->bh);
+ iloc->bh = NULL;
+ return 0;
+}
+
+static int resize_mark_inode_dirty_stub(handle_t *handle, struct inode *inode,
+ const char *func, unsigned int line)
+{
+ return 0;
+}
+
+static void resize_free_blocks_stub(handle_t *handle, struct inode *inode,
+ struct buffer_head *bh, ext4_fsblk_t block,
+ unsigned long count, int flags)
+{
+ struct kunit *test = active_resize_test;
+ struct resize_test_ctx *ctx;
+
+ if (!test)
+ return;
+ ctx = test->priv;
+ if (!ctx)
+ return;
+
+ ctx->last_freed_blk = block;
+ ctx->last_freed_count = count;
+ if (bh)
+ brelse(bh);
+}
+
+static void resize_test_cleanup(struct resize_test_ctx *ctx)
+{
+ struct super_block *sb = ctx->sb;
+ struct ext4_sb_info *sbi = &ctx->sbi;
+ struct flex_groups **flex_groups;
+ struct resize_mock_blk *blk, *tmp;
+ unsigned int i;
+
+ if (sb) {
+ cancel_work_sync(&sbi->s_sb_upd_work);
+ timer_delete_sync(&sbi->s_err_report);
+ }
+
+ if (ctx->resize_inode) {
+ iput(ctx->resize_inode);
+ ctx->resize_inode = NULL;
+ }
+
+ if (ctx->mb_inited && sb) {
+ ext4_mb_release(sb);
+ ctx->mb_inited = false;
+ }
+
+ if (ctx->counters_inited) {
+ percpu_counter_destroy(&sbi->s_freeclusters_counter);
+ percpu_counter_destroy(&sbi->s_dirtyclusters_counter);
+ percpu_counter_destroy(&sbi->s_freeinodes_counter);
+ ctx->counters_inited = false;
+ }
+
+ flex_groups = rcu_access_pointer(sbi->s_flex_groups);
+ if (flex_groups) {
+ for (i = 0; i < sbi->s_flex_groups_allocated; i++)
+ kvfree(flex_groups[i]);
+ kvfree(flex_groups);
+ rcu_assign_pointer(sbi->s_flex_groups, NULL);
+ sbi->s_flex_groups_allocated = 0;
+ }
+
+ rcu_barrier();
+ kvfree(rcu_access_pointer(sbi->s_group_desc));
+ rcu_assign_pointer(sbi->s_group_desc, NULL);
+
+ if (sb) {
+ deactivate_super(sb);
+ ctx->sb = NULL;
+ }
+
+ list_for_each_entry_safe(blk, tmp, &ctx->blks, node) {
+ list_del(&blk->node);
+ kfree(blk->data);
+ kfree(blk);
+ }
+
+ if (ctx->bh_folio) {
+ folio_put(ctx->bh_folio);
+ ctx->bh_folio = NULL;
+ }
+
+ if (ctx->bdev.bd_stats) {
+ free_percpu(ctx->bdev.bd_stats);
+ ctx->bdev.bd_stats = NULL;
+ }
+
+ active_resize_test = NULL;
+}
+
+KUNIT_DEFINE_ACTION_WRAPPER(resize_test_cleanup_action, resize_test_cleanup,
+ struct resize_test_ctx *);
+
+static int resize_test_set_super(struct super_block *sb, struct fs_context *fc)
+{
+ return set_anon_super_fc(sb, fc);
+}
+
+/*
+ * Configure an in-memory ext4 filesystem with @ngroups initial groups,
+ * optional @reserved_gdb reserved GDT blocks (enabling RESIZE_INODE and
+ * populating the resize inode's DIND + reserved GDT blocks), optional
+ * flex_bg (@log_flex > 0), and @last_grp_blks blocks in the final group.
+ */
+static void resize_configure_fs(struct kunit *test, ext4_group_t ngroups,
+ u16 reserved_gdb, u8 log_flex,
+ u32 last_grp_blks)
+{
+ struct resize_test_ctx *ctx = test->priv;
+ struct super_block *sb = ctx->sb;
+ struct ext4_sb_info *sbi = &ctx->sbi;
+ struct ext4_super_block *es = ctx->es;
+ struct buffer_head **group_desc;
+ unsigned long gdb_count;
+ ext4_fsblk_t total_blks = RESIZE_TEST_FIRST_DATA_BLK;
+ ext4_fsblk_t total_free_blks = 0;
+ u32 total_inodes = ngroups * RESIZE_TEST_INODES_PER_GRP;
+ ext4_group_t g;
+ int ret;
+
+ KUNIT_ASSERT_GT(test, ngroups, 0U);
+ KUNIT_ASSERT_LE(test, last_grp_blks, RESIZE_TEST_BLOCKS_PER_GRP);
+
+ es->s_magic = cpu_to_le16(EXT4_SUPER_MAGIC);
+ es->s_log_block_size = cpu_to_le32(0);
+ es->s_log_cluster_size = cpu_to_le32(0);
+ es->s_first_data_block = cpu_to_le32(RESIZE_TEST_FIRST_DATA_BLK);
+ es->s_blocks_per_group = cpu_to_le32(RESIZE_TEST_BLOCKS_PER_GRP);
+ es->s_clusters_per_group = cpu_to_le32(RESIZE_TEST_BLOCKS_PER_GRP);
+ es->s_inodes_per_group = cpu_to_le32(RESIZE_TEST_INODES_PER_GRP);
+ es->s_inodes_count = cpu_to_le32(total_inodes);
+ es->s_free_inodes_count = cpu_to_le32(total_inodes - 10);
+ es->s_first_ino = cpu_to_le32(11);
+ es->s_inode_size = cpu_to_le16(RESIZE_TEST_INODE_SIZE);
+ es->s_desc_size = cpu_to_le16(RESIZE_TEST_DESC_SIZE);
+ es->s_rev_level = cpu_to_le32(EXT4_DYNAMIC_REV);
+ es->s_state = cpu_to_le16(EXT4_VALID_FS);
+ es->s_errors = cpu_to_le16(EXT4_ERRORS_CONTINUE);
+ es->s_checksum_type = EXT4_CRC32C_CHKSUM;
+ es->s_reserved_gdt_blocks = cpu_to_le16(reserved_gdb);
+ generate_random_uuid(es->s_uuid);
+
+ ext4_set_feature_extents(sb);
+ ext4_set_feature_64bit(sb);
+ ext4_set_feature_filetype(sb);
+ ext4_set_feature_sparse_super(sb);
+ ext4_set_feature_metadata_csum(sb);
+ if (reserved_gdb > 0)
+ ext4_set_feature_resize_inode(sb);
+ if (log_flex > 0) {
+ ext4_set_feature_flex_bg(sb);
+ es->s_log_groups_per_flex = log_flex;
+ sbi->s_log_groups_per_flex = log_flex;
+ }
+
+ sbi->s_csum_seed = ext4_chksum(~0, es->s_uuid, sizeof(es->s_uuid));
+ sbi->s_groups_count = ngroups;
+ gdb_count = DIV_ROUND_UP(ngroups, RESIZE_TEST_DESC_PER_BLK);
+ sbi->s_gdb_count = gdb_count;
+
+ group_desc = kvmalloc_array(gdb_count, sizeof(*group_desc), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, group_desc);
+ for (g = 0; g < gdb_count; g++) {
+ struct buffer_head *bh;
+
+ bh = resize_get_or_create_bh(ctx,
+ RESIZE_TEST_FIRST_DATA_BLK + 1 + g);
+ KUNIT_ASSERT_NOT_NULL(test, bh);
+ brelse(bh);
+ group_desc[g] = bh;
+ }
+ rcu_assign_pointer(sbi->s_group_desc, group_desc);
+
+ for (g = 0; g < ngroups; g++) {
+ u32 grp_blks = (g == ngroups - 1) ?
+ last_grp_blks : RESIZE_TEST_BLOCKS_PER_GRP;
+ ext4_fsblk_t start = ext4_group_first_block_no(sb, g);
+ unsigned int overhead = ext4_bg_num_gdb(sb, g);
+ unsigned int used_meta;
+ ext4_fsblk_t bb_blk, ib_blk, it_blk;
+ struct buffer_head *bb_bh, *ib_bh;
+ struct ext4_group_desc *gdp;
+ u32 free_blks;
+
+ if (ext4_bg_has_super(sb, g))
+ overhead += 1 + reserved_gdb;
+
+ bb_blk = start + overhead;
+ ib_blk = bb_blk + 1;
+ it_blk = ib_blk + 1;
+ used_meta = overhead + 2 + RESIZE_TEST_ITB_PER_GRP;
+ if (g == 0 && reserved_gdb > 0)
+ used_meta++;
+
+ bb_bh = resize_get_or_create_bh(ctx, bb_blk);
+ ib_bh = resize_get_or_create_bh(ctx, ib_blk);
+ KUNIT_ASSERT_NOT_NULL(test, bb_bh);
+ KUNIT_ASSERT_NOT_NULL(test, ib_bh);
+
+ mb_set_bits(bb_bh->b_data, 0, used_meta);
+ ext4_mark_bitmap_end(grp_blks, sb->s_blocksize * 8,
+ bb_bh->b_data);
+ ext4_mark_bitmap_end(RESIZE_TEST_INODES_PER_GRP,
+ sb->s_blocksize * 8, ib_bh->b_data);
+
+ gdp = ext4_get_group_desc(sb, g, NULL);
+ KUNIT_ASSERT_NOT_NULL(test, gdp);
+ memset(gdp, 0, RESIZE_TEST_DESC_SIZE);
+ ext4_block_bitmap_set(sb, gdp, bb_blk);
+ ext4_inode_bitmap_set(sb, gdp, ib_blk);
+ ext4_inode_table_set(sb, gdp, it_blk);
+ free_blks = grp_blks - used_meta;
+ ext4_free_group_clusters_set(sb, gdp, free_blks);
+ ext4_free_inodes_set(sb, gdp, RESIZE_TEST_INODES_PER_GRP);
+ ext4_itable_unused_set(sb, gdp, RESIZE_TEST_INODES_PER_GRP);
+ gdp->bg_flags = cpu_to_le16(EXT4_BG_INODE_ZEROED);
+ ext4_block_bitmap_csum_set(sb, gdp, bb_bh);
+ ext4_inode_bitmap_csum_set(sb, gdp, ib_bh);
+ ext4_group_desc_csum_set(sb, g, gdp);
+
+ brelse(bb_bh);
+ brelse(ib_bh);
+
+ total_blks += grp_blks;
+ total_free_blks += free_blks;
+ }
+
+ ext4_blocks_count_set(es, total_blks);
+ ext4_free_blocks_count_set(es, total_free_blks);
+ ext4_r_blocks_count_set(es, total_blks / 20);
+ sbi->s_blockfile_groups = ngroups;
+
+ if (reserved_gdb > 0) {
+ ext4_fsblk_t dind_blk;
+ struct buffer_head *dind_bh;
+ __le32 *dind_data;
+ unsigned int res;
+ unsigned int total_rsv_blks = 1;
+
+ dind_blk = ext4_group_first_block_no(sb, 0) + 1 + gdb_count +
+ reserved_gdb + 2 + RESIZE_TEST_ITB_PER_GRP;
+ dind_bh = resize_get_or_create_bh(ctx, dind_blk);
+ KUNIT_ASSERT_NOT_NULL(test, dind_bh);
+ dind_data = (__le32 *)dind_bh->b_data;
+
+ for (res = 0; res < reserved_gdb; res++) {
+ ext4_fsblk_t pri_blk = RESIZE_TEST_FIRST_DATA_BLK + 1 +
+ gdb_count + res;
+ struct buffer_head *pri_bh;
+ __le32 *pri_data;
+ unsigned int three = 1, five = 5, seven = 7;
+ unsigned int bgrp, idx = 0;
+
+ dind_data[(gdb_count + res) %
+ EXT4_ADDR_PER_BLOCK(sb)] =
+ cpu_to_le32(pri_blk);
+ pri_bh = resize_get_or_create_bh(ctx, pri_blk);
+ KUNIT_ASSERT_NOT_NULL(test, pri_bh);
+ pri_data = (__le32 *)pri_bh->b_data;
+
+ while ((bgrp = ext4_list_backups(sb, &three, &five,
+ &seven)) < ngroups) {
+ pri_data[idx++] = cpu_to_le32(
+ bgrp * RESIZE_TEST_BLOCKS_PER_GRP +
+ pri_blk);
+ }
+ total_rsv_blks += 1 + idx;
+ brelse(pri_bh);
+ }
+ brelse(dind_bh);
+
+ ctx->resize_inode = new_inode(sb);
+ KUNIT_ASSERT_NOT_NULL(test, ctx->resize_inode);
+ ctx->resize_inode->i_ino = EXT4_RESIZE_INO;
+ ctx->resize_inode->i_mode = S_IFREG | 0600;
+ ctx->resize_inode->i_blkbits = RESIZE_TEST_BLK_BITS;
+ ctx->resize_inode->i_blocks = total_rsv_blks *
+ (RESIZE_TEST_BLK_SIZE >> 9);
+ EXT4_I(ctx->resize_inode)->i_data[EXT4_DIND_BLOCK] =
+ cpu_to_le32(dind_blk);
+ }
+
+ if (log_flex > 0) {
+ ret = ext4_fill_flex_info_test(sb);
+ KUNIT_ASSERT_EQ(test, ret, 1);
+ }
+
+ ret = ext4_mb_init(sb);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+ ctx->mb_inited = true;
+
+ ret = percpu_counter_init(&sbi->s_freeclusters_counter,
+ total_free_blks, GFP_KERNEL);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+ ret = percpu_counter_init(&sbi->s_dirtyclusters_counter, 0, GFP_KERNEL);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+ ret = percpu_counter_init(&sbi->s_freeinodes_counter,
+ total_inodes - 10, GFP_KERNEL);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+ ctx->counters_inited = true;
+
+ ext4_calculate_overhead(sb);
+ es->s_overhead_clusters = cpu_to_le32(sbi->s_overhead);
+ ext4_superblock_csum_set(sb);
+}
+
+static int resize_test_init(struct kunit *test)
+{
+ struct resize_test_ctx *ctx;
+ struct fs_context *fc;
+ struct super_block *sb;
+ struct ext4_sb_info *sbi;
+ struct buffer_head *sbh;
+ int ret;
+
+ ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, ctx);
+ INIT_LIST_HEAD(&ctx->blks);
+ ctx->device_blocks = 1 + 64 * RESIZE_TEST_BLOCKS_PER_GRP;
+ test->priv = ctx;
+
+ ctx->bdev.bd_stats = alloc_percpu(struct disk_stats);
+ KUNIT_ASSERT_NOT_NULL(test, ctx->bdev.bd_stats);
+
+ ctx->bh_folio = folio_alloc(GFP_KERNEL, 0);
+ if (!ctx->bh_folio) {
+ free_percpu(ctx->bdev.bd_stats);
+ ctx->bdev.bd_stats = NULL;
+ KUNIT_FAIL_AND_ABORT(test, "folio_alloc failed");
+ }
+
+ fc = fs_context_for_mount(&resize_test_fs_type, 0);
+ if (IS_ERR(fc)) {
+ folio_put(ctx->bh_folio);
+ ctx->bh_folio = NULL;
+ free_percpu(ctx->bdev.bd_stats);
+ ctx->bdev.bd_stats = NULL;
+ KUNIT_FAIL_AND_ABORT(test, "fs_context_for_mount failed: %ld",
+ PTR_ERR(fc));
+ }
+ sb = sget_fc(fc, NULL, resize_test_set_super);
+ put_fs_context(fc);
+ if (IS_ERR(sb)) {
+ folio_put(ctx->bh_folio);
+ ctx->bh_folio = NULL;
+ free_percpu(ctx->bdev.bd_stats);
+ ctx->bdev.bd_stats = NULL;
+ KUNIT_FAIL_AND_ABORT(test, "sget_fc failed: %ld", PTR_ERR(sb));
+ }
+ ctx->sb = sb;
+ up_write(&sb->s_umount);
+
+ ret = kunit_add_action_or_reset(test, resize_test_cleanup_action, ctx);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ sbi = &ctx->sbi;
+ sb->s_fs_info = sbi;
+ sb->s_op = &resize_test_sops;
+ sb->s_blocksize = RESIZE_TEST_BLK_SIZE;
+ sb->s_blocksize_bits = RESIZE_TEST_BLK_BITS;
+ sb->s_magic = EXT4_SUPER_MAGIC;
+ sb->s_maxbytes = MAX_LFS_FILESIZE;
+ INIT_LIST_HEAD(&sb->s_inodes);
+#ifdef CONFIG_FS_ENCRYPTION
+ sb->s_cop = &ext4_cryptops;
+#endif
+
+ strscpy(sb->s_id, "ext4_resize_kunit", sizeof(sb->s_id));
+ inode_init_once(&ctx->bd_inode);
+ ctx->bd_inode.i_sb = sb;
+ ctx->bd_inode.i_blkbits = RESIZE_TEST_BLK_BITS;
+ ctx->bd_inode.i_mapping = &ctx->bd_inode.i_data;
+ ctx->bd_inode.i_data.host = &ctx->bd_inode;
+ ctx->bdev_disk.part0 = &ctx->bdev;
+ 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->bd_inode.i_data;
+ bdev_clear_flag(&ctx->bdev, BD_READ_ONLY);
+ sb->s_bdev = &ctx->bdev;
+
+ sbi->s_sb = sb;
+ sbi->s_blockgroup_lock = &ctx->bgl;
+ bgl_lock_init(&ctx->bgl);
+ INIT_WORK(&sbi->s_sb_upd_work, resize_test_sb_upd_work);
+ timer_setup(&sbi->s_err_report, resize_test_err_timer, 0);
+ spin_lock_init(&sbi->s_error_lock);
+ spin_lock_init(&sbi->s_md_lock);
+ spin_lock_init(&sbi->s_bdev_wb_lock);
+ spin_lock_init(&sbi->s_es_lock);
+ INIT_LIST_HEAD(&sbi->s_es_list);
+ mutex_init(&sbi->s_fc_lock);
+ INIT_LIST_HEAD(&sbi->s_fc_q[FC_Q_MAIN]);
+ INIT_LIST_HEAD(&sbi->s_fc_q[FC_Q_STAGING]);
+ INIT_LIST_HEAD(&sbi->s_fc_dentry_q[FC_Q_MAIN]);
+ INIT_LIST_HEAD(&sbi->s_fc_dentry_q[FC_Q_STAGING]);
+ ratelimit_state_init(&sbi->s_err_ratelimit_state, 5 * HZ, 10);
+ ratelimit_state_init(&sbi->s_warning_ratelimit_state, 5 * HZ, 10);
+ ratelimit_state_init(&sbi->s_msg_ratelimit_state, 5 * HZ, 10);
+
+ sbi->s_blocks_per_group = RESIZE_TEST_BLOCKS_PER_GRP;
+ sbi->s_clusters_per_group = RESIZE_TEST_BLOCKS_PER_GRP;
+ sbi->s_cluster_ratio = 1;
+ sbi->s_cluster_bits = 0;
+ sbi->s_inodes_per_group = RESIZE_TEST_INODES_PER_GRP;
+ sbi->s_inode_size = RESIZE_TEST_INODE_SIZE;
+ sbi->s_inodes_per_block = RESIZE_TEST_BLK_SIZE / RESIZE_TEST_INODE_SIZE;
+ sbi->s_itb_per_group = RESIZE_TEST_ITB_PER_GRP;
+ sbi->s_desc_size = RESIZE_TEST_DESC_SIZE;
+ sbi->s_desc_per_block = RESIZE_TEST_DESC_PER_BLK;
+ sbi->s_desc_per_block_bits = RESIZE_TEST_DESC_PER_BLK_BITS;
+ sbi->s_first_ino = 11;
+ sbi->s_mount_state = EXT4_VALID_FS;
+ set_opt(sb, ERRORS_CONT);
+
+ sbh = resize_get_or_create_bh(ctx, RESIZE_TEST_FIRST_DATA_BLK);
+ KUNIT_ASSERT_NOT_NULL(test, sbh);
+ brelse(sbh);
+ sbi->s_sbh = sbh;
+ ctx->es = (struct ext4_super_block *)sbh->b_data;
+ sbi->s_es = ctx->es;
+
+ resize_init_mock_bh(&ctx->iloc_bh, ctx->bh_folio, ctx->iloc_buf,
+ RESIZE_TEST_BLK_SIZE, 99);
+
+ active_resize_test = test;
+ kunit_activate_static_stub(test, ext4_sb_getblk, resize_sb_getblk_stub);
+ kunit_activate_static_stub(test, ext4_sb_bread, resize_sb_bread_stub);
+ kunit_activate_static_stub(test, ext4_resize_issue_zeroout,
+ resize_zeroout_stub);
+ kunit_activate_static_stub(test, __ext4_iget, resize_iget_stub);
+ kunit_activate_static_stub(test, ext4_get_inode_loc,
+ resize_get_inode_loc_stub);
+ kunit_activate_static_stub(test, ext4_mark_iloc_dirty,
+ resize_mark_iloc_dirty_stub);
+ kunit_activate_static_stub(test, __ext4_mark_inode_dirty,
+ resize_mark_inode_dirty_stub);
+ kunit_activate_static_stub(test, ext4_free_blocks,
+ resize_free_blocks_stub);
+
+ return 0;
+}
+
+/*
+ * Test 1: ext4_resize_begin() precondition checks and EXT4_FLAGS_RESIZING
+ * mutual exclusion with ext4_resize_end().
+ */
+static void test_resize_begin_and_end_guards(struct kunit *test)
+{
+ struct resize_test_ctx *ctx = test->priv;
+ struct super_block *sb = ctx->sb;
+
+ resize_configure_fs(test, 2, 0, 0, RESIZE_TEST_BLOCKS_PER_GRP);
+
+ /* Non-zero s_reserved_gdt_blocks without resize_inode -> -EFSCORRUPTED */
+ ctx->es->s_reserved_gdt_blocks = cpu_to_le16(2);
+ KUNIT_EXPECT_EQ(test, ext4_resize_begin(sb), -EFSCORRUPTED);
+ ctx->es->s_reserved_gdt_blocks = 0;
+ ctx->sbi.s_mount_state &= ~EXT4_ERROR_FS;
+
+ /* Mounted from backup superblock -> -EPERM */
+ ctx->sbi.s_sbh->b_blocknr = 1025;
+ KUNIT_EXPECT_EQ(test, ext4_resize_begin(sb), -EPERM);
+ ctx->sbi.s_sbh->b_blocknr = RESIZE_TEST_FIRST_DATA_BLK;
+
+ /* Filesystem has recorded errors -> -EPERM */
+ ctx->sbi.s_mount_state |= EXT4_ERROR_FS;
+ KUNIT_EXPECT_EQ(test, ext4_resize_begin(sb), -EPERM);
+ ctx->sbi.s_mount_state &= ~EXT4_ERROR_FS;
+
+ /* sparse_super2 online resize unsupported -> -EOPNOTSUPP */
+ ext4_set_feature_sparse_super2(sb);
+ KUNIT_EXPECT_EQ(test, ext4_resize_begin(sb), -EOPNOTSUPP);
+ ext4_clear_feature_sparse_super2(sb);
+
+ /* Valid begin acquires EXT4_FLAGS_RESIZING; second call gets -EBUSY */
+ KUNIT_EXPECT_EQ(test, ext4_resize_begin(sb), 0);
+ KUNIT_EXPECT_TRUE(test, test_bit(EXT4_FLAGS_RESIZING,
+ &ctx->sbi.s_ext4_flags));
+ KUNIT_EXPECT_EQ(test, ext4_resize_begin(sb), -EBUSY);
+
+ /* ext4_resize_end releases lock so subsequent begin succeeds */
+ KUNIT_EXPECT_EQ(test, ext4_resize_end(sb, false), 0);
+ KUNIT_EXPECT_FALSE(test, test_bit(EXT4_FLAGS_RESIZING,
+ &ctx->sbi.s_ext4_flags));
+ KUNIT_EXPECT_EQ(test, ext4_resize_begin(sb), 0);
+ KUNIT_EXPECT_EQ(test, ext4_resize_end(sb, false), 0);
+}
+
+/*
+ * Test 2: ext4_list_backups() sequence generation (sparse_super, dense, and
+ * sparse_super2) and verify_reserved_gdb() validation.
+ */
+static void test_list_backups_and_verify_reserved_gdb(struct kunit *test)
+{
+ struct resize_test_ctx *ctx = test->priv;
+ struct super_block *sb = ctx->sb;
+ static const unsigned int expected_sparse[] = {
+ 1, 3, 5, 7, 9, 25, 27, 49, 81
+ };
+ unsigned int three, five, seven;
+ struct buffer_head *pri_bh;
+ __le32 *pri_data;
+ unsigned int i;
+
+ resize_configure_fs(test, 4, 0, 0, RESIZE_TEST_BLOCKS_PER_GRP);
+
+ /* 1. sparse_super yields sorted powers of 3, 5, and 7 */
+ three = 1;
+ five = 5;
+ seven = 7;
+ for (i = 0; i < ARRAY_SIZE(expected_sparse); i++) {
+ KUNIT_EXPECT_EQ_MSG(test,
+ ext4_list_backups(sb, &three, &five, &seven),
+ expected_sparse[i], "sparse index %u", i);
+ }
+
+ /* 2. Without sparse_super: every consecutive group 1, 2, 3, 4 */
+ ext4_clear_feature_sparse_super(sb);
+ three = 1;
+ five = 5;
+ seven = 7;
+ for (i = 1; i <= 4; i++)
+ KUNIT_EXPECT_EQ(test,
+ ext4_list_backups(sb, &three, &five, &seven),
+ i);
+ ext4_set_feature_sparse_super(sb);
+
+ /* 3. sparse_super2: reads s_backup_bgs[0..1], skipping 0, then UINT_MAX */
+ ext4_set_feature_sparse_super2(sb);
+ ctx->es->s_backup_bgs[0] = 0;
+ ctx->es->s_backup_bgs[1] = cpu_to_le32(12);
+ three = 1;
+ five = 5;
+ seven = 7;
+ KUNIT_EXPECT_EQ(test, ext4_list_backups(sb, &three, &five, &seven), 12U);
+ KUNIT_EXPECT_EQ(test, ext4_list_backups(sb, &three, &five, &seven),
+ UINT_MAX);
+ ext4_clear_feature_sparse_super2(sb);
+
+ /* 4. verify_reserved_gdb: valid backup list for groups < 10 (5 backups) */
+ pri_bh = resize_get_or_create_bh(ctx, 3);
+ KUNIT_ASSERT_NOT_NULL(test, pri_bh);
+ pri_data = (__le32 *)pri_bh->b_data;
+ for (i = 0; i < 5; i++)
+ pri_data[i] = cpu_to_le32(expected_sparse[i] *
+ RESIZE_TEST_BLOCKS_PER_GRP + 3);
+ KUNIT_EXPECT_EQ(test, ext4_verify_reserved_gdb_test(sb, 10, pri_bh), 5);
+
+ /* Corrupted backup block entry -> -EINVAL */
+ pri_data[2] = cpu_to_le32(99999);
+ KUNIT_EXPECT_EQ(test, ext4_verify_reserved_gdb_test(sb, 10, pri_bh),
+ -EINVAL);
+
+ /* Exceeding EXT4_ADDR_PER_BLOCK (256 on 1K blocks) -> -EFBIG */
+ ext4_clear_feature_sparse_super(sb);
+ for (i = 0; i <= 257 && i < RESIZE_TEST_BLK_SIZE / sizeof(__le32); i++)
+ pri_data[i] = cpu_to_le32((i + 1) * RESIZE_TEST_BLOCKS_PER_GRP +
+ 3);
+ /*
+ * Allocate an oversized buffer temporarily so 257 entries can match
+ * before hitting ++gdbackups > EXT4_ADDR_PER_BLOCK(sb) (256).
+ */
+ {
+ __le32 *big_buf = kunit_kcalloc(test, 260, sizeof(__le32),
+ GFP_KERNEL);
+ u8 *orig_data = (u8 *)pri_bh->b_data;
+
+ KUNIT_ASSERT_NOT_NULL(test, big_buf);
+ for (i = 0; i < 258; i++)
+ big_buf[i] = cpu_to_le32((i + 1) *
+ RESIZE_TEST_BLOCKS_PER_GRP +
+ 3);
+ pri_bh->b_data = (char *)big_buf;
+ KUNIT_EXPECT_EQ(test,
+ ext4_verify_reserved_gdb_test(sb, 259, pri_bh),
+ -EFBIG);
+ pri_bh->b_data = (char *)orig_data;
+ }
+ brelse(pri_bh);
+}
+
+/*
+ * Test 3: verify_group_input() boundary and overlap checks when adding sparse
+ * backup group 3 (which carries superblock + GDT overhead at group start).
+ */
+static void test_verify_group_input_rejections_and_acceptance(struct kunit *test)
+{
+ struct resize_test_ctx *ctx = test->priv;
+ struct super_block *sb = ctx->sb;
+ struct ext4_new_group_data input;
+ ext4_fsblk_t start;
+ unsigned int overhead;
+
+ /* Configure 3 full groups (0..2); next group to add is sparse group 3 */
+ resize_configure_fs(test, 3, 1, 0, RESIZE_TEST_BLOCKS_PER_GRP);
+ start = ext4_blocks_count(ctx->es);
+ /* Group 3 has backup super (1) + 1 GDT + 1 reserved GDT = 3 blocks */
+ overhead = 3;
+
+ memset(&input, 0, sizeof(input));
+ input.group = 3;
+ input.blocks_count = RESIZE_TEST_BLOCKS_PER_GRP;
+ input.reserved_blocks = 50;
+ input.block_bitmap = start + overhead;
+ input.inode_bitmap = start + overhead + 1;
+ input.inode_table = start + overhead + 2;
+
+ /* 1. Wrong group number */
+ input.group = 4;
+ KUNIT_EXPECT_EQ(test, ext4_verify_group_input_test(sb, &input),
+ -EINVAL);
+ input.group = 3;
+
+ /* 2. Previous last group not full */
+ ext4_blocks_count_set(ctx->es, start - 10);
+ KUNIT_EXPECT_EQ(test, ext4_verify_group_input_test(sb, &input),
+ -EINVAL);
+ ext4_blocks_count_set(ctx->es, start);
+
+ /* 3. reserved_blocks > blocks_count / 5 */
+ input.reserved_blocks = 205;
+ KUNIT_EXPECT_EQ(test, ext4_verify_group_input_test(sb, &input),
+ -EINVAL);
+ input.reserved_blocks = 50;
+
+ /* 4. blocks_count smaller than metadata (free_blocks_count < 0) */
+ input.blocks_count = 10;
+ KUNIT_EXPECT_EQ(test, ext4_verify_group_input_test(sb, &input),
+ -EINVAL);
+ input.blocks_count = RESIZE_TEST_BLOCKS_PER_GRP;
+
+ /* 5. Last block beyond underlying device -> -EIO */
+ ctx->device_blocks = start + 100;
+ KUNIT_EXPECT_EQ(test, ext4_verify_group_input_test(sb, &input), -EIO);
+ ctx->device_blocks = 1 + 64 * RESIZE_TEST_BLOCKS_PER_GRP;
+
+ /* 6. Bitmaps or inode table outside [start, end) */
+ input.block_bitmap = start - 1;
+ KUNIT_EXPECT_EQ(test, ext4_verify_group_input_test(sb, &input),
+ -EINVAL);
+ input.block_bitmap = start + overhead;
+
+ input.inode_bitmap = start + RESIZE_TEST_BLOCKS_PER_GRP;
+ KUNIT_EXPECT_EQ(test, ext4_verify_group_input_test(sb, &input),
+ -EINVAL);
+ input.inode_bitmap = start + overhead + 1;
+
+ input.inode_table = start + RESIZE_TEST_BLOCKS_PER_GRP -
+ RESIZE_TEST_ITB_PER_GRP + 1;
+ KUNIT_EXPECT_EQ(test, ext4_verify_group_input_test(sb, &input),
+ -EINVAL);
+ input.inode_table = start + overhead + 2;
+
+ /* 7. Bitmap collision and bitmap inside inode table */
+ input.inode_bitmap = input.block_bitmap;
+ KUNIT_EXPECT_EQ(test, ext4_verify_group_input_test(sb, &input),
+ -EINVAL);
+ input.inode_bitmap = input.inode_table + 3;
+ KUNIT_EXPECT_EQ(test, ext4_verify_group_input_test(sb, &input),
+ -EINVAL);
+ input.inode_bitmap = start + overhead + 1;
+
+ input.block_bitmap = input.inode_table;
+ KUNIT_EXPECT_EQ(test, ext4_verify_group_input_test(sb, &input),
+ -EINVAL);
+ input.block_bitmap = start + overhead;
+
+ /* 8. Bitmaps or inode table overlapping superblock/GDT overhead */
+ input.block_bitmap = start + overhead - 1;
+ KUNIT_EXPECT_EQ(test, ext4_verify_group_input_test(sb, &input),
+ -EINVAL);
+ input.block_bitmap = start + overhead;
+
+ input.inode_bitmap = start;
+ KUNIT_EXPECT_EQ(test, ext4_verify_group_input_test(sb, &input),
+ -EINVAL);
+ input.inode_bitmap = start + overhead + 1;
+
+ input.inode_table = start + 1;
+ KUNIT_EXPECT_EQ(test, ext4_verify_group_input_test(sb, &input),
+ -EINVAL);
+ input.inode_table = start + overhead + 2;
+
+ /* 9. Valid group input accepted with exact free_clusters_count */
+ KUNIT_EXPECT_EQ(test, ext4_verify_group_input_test(sb, &input), 0);
+ /* 1024 - 2 (bitmaps) - 3 (overhead) - 8 (itable) = 1011 */
+ KUNIT_EXPECT_EQ(test, input.free_clusters_count, 1011U);
+}
+
+/*
+ * Test 4: alloc_flex_gd(), ext4_setup_next_flex_gd(), and
+ * ext4_alloc_group_tables() packed flex_bg layout and -ENOSPC handling.
+ */
+static void test_alloc_setup_flex_gd_and_group_tables(struct kunit *test)
+{
+ struct resize_test_ctx *ctx = test->priv;
+ struct super_block *sb = ctx->sb;
+ struct ext4_new_flex_group_data *flex_gd;
+ ext4_fsblk_t g4_start;
+
+ /* 4 initial groups (1 flex_bg of size 4, log_flex = 2), meta_bg mode */
+ resize_configure_fs(test, 4, 0, 2, RESIZE_TEST_BLOCKS_PER_GRP);
+ ext4_set_feature_meta_bg(sb);
+ ctx->es->s_first_meta_bg = 0;
+
+ /* 1. alloc_flex_gd shrinks allocation when adding only 1 group (4..4) */
+ flex_gd = ext4_alloc_flex_gd_test(16, 4, 4);
+ KUNIT_ASSERT_NOT_NULL(test, flex_gd);
+ KUNIT_EXPECT_EQ(test, flex_gd->resize_bg, 1U);
+ ext4_free_flex_gd_test(flex_gd);
+
+ /* 2. Full flex_bg batch (groups 4..7, with group 7 partial = 500 blks) */
+ flex_gd = ext4_alloc_flex_gd_test(4, 3, 7);
+ KUNIT_ASSERT_NOT_NULL(test, flex_gd);
+ KUNIT_EXPECT_EQ(test, flex_gd->resize_bg, 4U);
+
+ /* No-op when target equals current blocks_count */
+ KUNIT_EXPECT_EQ(test,
+ ext4_setup_next_flex_gd_test(sb, flex_gd,
+ ext4_blocks_count(ctx->es)),
+ 0);
+
+ /* Setup groups 4, 5, 6 (1024 blks each) + group 7 (500 blks) */
+ KUNIT_EXPECT_EQ(test,
+ ext4_setup_next_flex_gd_test(sb, flex_gd,
+ 1 + 7 * 1024 + 500),
+ 1);
+ KUNIT_EXPECT_EQ(test, flex_gd->count, 4U);
+ KUNIT_EXPECT_EQ(test, flex_gd->groups[0].group, 4U);
+ KUNIT_EXPECT_EQ(test, flex_gd->groups[3].group, 7U);
+ KUNIT_EXPECT_EQ(test, flex_gd->groups[3].blocks_count, 500U);
+ /* Final group clears BG_BLOCK_UNINIT under group_desc_csum */
+ KUNIT_EXPECT_NE(test, flex_gd->bg_flags[0] & EXT4_BG_BLOCK_UNINIT, 0);
+ KUNIT_EXPECT_EQ(test, flex_gd->bg_flags[3] & EXT4_BG_BLOCK_UNINIT, 0);
+
+ /*
+ * 3. ext4_alloc_group_tables: group 4 has no super/meta_bg overhead
+ * (group 5 has sparse backup super -> overhead=1, stopping contiguous
+ * span at end of group 4). All 4 block bitmaps, 4 inode bitmaps, and
+ * 4*8=32 inode table blocks (40 blocks total) pack into group 4!
+ */
+ g4_start = ext4_group_first_block_no(sb, 4);
+ KUNIT_EXPECT_EQ(test, ext4_alloc_group_tables_test(sb, flex_gd, 4), 0);
+ KUNIT_EXPECT_EQ(test, flex_gd->groups[0].block_bitmap, g4_start);
+ KUNIT_EXPECT_EQ(test, flex_gd->groups[3].block_bitmap, g4_start + 3);
+ KUNIT_EXPECT_EQ(test, flex_gd->groups[0].inode_bitmap, g4_start + 4);
+ KUNIT_EXPECT_EQ(test, flex_gd->groups[3].inode_bitmap, g4_start + 7);
+ KUNIT_EXPECT_EQ(test, flex_gd->groups[0].inode_table, g4_start + 8);
+ KUNIT_EXPECT_EQ(test, flex_gd->groups[3].inode_table, g4_start + 32);
+ /* Group 4 hosts all 40 metadata blocks -> BG_BLOCK_UNINIT cleared */
+ KUNIT_EXPECT_EQ(test, flex_gd->bg_flags[0] & EXT4_BG_BLOCK_UNINIT, 0);
+ KUNIT_EXPECT_EQ(test, flex_gd->groups[0].free_clusters_count,
+ 1024U - 40U);
+
+ /* 4. Insufficient space in flex_gd returns -ENOSPC */
+ flex_gd->count = 1;
+ flex_gd->groups[0].group = 4;
+ flex_gd->groups[0].blocks_count = 5;
+ flex_gd->groups[0].mdata_blocks = 0;
+ KUNIT_EXPECT_EQ(test, ext4_alloc_group_tables_test(sb, flex_gd, 4),
+ -ENOSPC);
+
+ ext4_free_flex_gd_test(flex_gd);
+}
+
+/*
+ * Test 5: ext4_group_extend() validation and partial-last-group online
+ * extension updating superblock, block bitmap, buddy cache, and backup SB.
+ */
+static void test_group_extend_partial_last_group(struct kunit *test)
+{
+ struct resize_test_ctx *ctx = test->priv;
+ struct super_block *sb = ctx->sb;
+ struct ext4_super_block *es = ctx->es;
+ struct ext4_super_block *backup_es;
+ struct buffer_head *b1_bh;
+ ext4_fsblk_t o_blks, o_free;
+ s64 free_before;
+
+ /* 2 groups: group 0 full (1024 blks), group 1 partial (600 blks) */
+ resize_configure_fs(test, 2, 0, 0, 600);
+ o_blks = ext4_blocks_count(es);
+ o_free = ext4_free_blocks_count(es);
+ KUNIT_ASSERT_EQ(test, o_blks, 1ULL + 1024ULL + 600ULL);
+
+ /* No-op for 0 and o_blocks_count */
+ KUNIT_EXPECT_EQ(test, ext4_group_extend(sb, es, 0), 0);
+ KUNIT_EXPECT_EQ(test, ext4_group_extend(sb, es, o_blks), 0);
+
+ /* Shrink attempt -> -EINVAL */
+ KUNIT_EXPECT_EQ(test, ext4_group_extend(sb, es, o_blks - 10), -EINVAL);
+
+ /* Target beyond underlying device -> -ENOSPC */
+ ctx->device_blocks = o_blks + 100;
+ KUNIT_EXPECT_EQ(test, ext4_group_extend(sb, es, o_blks + 200), -ENOSPC);
+ ctx->device_blocks = 1 + 64 * RESIZE_TEST_BLOCKS_PER_GRP;
+
+ /* Extend group 1 by 300 blocks (from 600 to 900 blocks) */
+ free_before = percpu_counter_sum(&ctx->sbi.s_freeclusters_counter);
+ KUNIT_EXPECT_EQ(test, ext4_group_extend(sb, es, o_blks + 300), 0);
+ KUNIT_EXPECT_EQ(test, ext4_blocks_count(es), o_blks + 300);
+ KUNIT_EXPECT_EQ(test, ext4_free_blocks_count(es), o_free + 300);
+ KUNIT_EXPECT_EQ(test,
+ percpu_counter_sum(&ctx->sbi.s_freeclusters_counter),
+ free_before + 300);
+
+ /* Extend past group 1 boundary clamps to end of group 1 (+124 blocks) */
+ KUNIT_EXPECT_EQ(test, ext4_group_extend(sb, es, o_blks + 800), 0);
+ KUNIT_EXPECT_EQ(test, ext4_blocks_count(es), 1ULL + 2048ULL);
+
+ /* Once group 1 is full, further ext4_group_extend returns -EPERM */
+ KUNIT_EXPECT_EQ(test, ext4_group_extend(sb, es, 1ULL + 2200ULL),
+ -EPERM);
+
+ /* Backup superblock in group 1 (block 1025) reflects new size */
+ b1_bh = resize_lookup_bh(ctx, ext4_group_first_block_no(sb, 1));
+ KUNIT_ASSERT_NOT_NULL(test, b1_bh);
+ backup_es = (struct ext4_super_block *)b1_bh->b_data;
+ KUNIT_EXPECT_EQ(test, ext4_blocks_count(backup_es), 1ULL + 2048ULL);
+ KUNIT_EXPECT_EQ(test, le16_to_cpu(backup_es->s_block_group_nr), 1U);
+}
+
+/*
+ * Test 6: ext4_group_add() error guards and adding non-sparse group 2 plus
+ * sparse backup group 3 (exercising reserve_backup_gdb() on resize_inode).
+ */
+static void test_group_add_with_resize_inode_and_backup_gdb(struct kunit *test)
+{
+ struct resize_test_ctx *ctx = test->priv;
+ struct super_block *sb = ctx->sb;
+ struct ext4_new_group_data input;
+ struct buffer_head *pri_bh;
+ __le32 *pri_data;
+ blkcnt_t rsv_iblocks_before;
+ ext4_fsblk_t start;
+
+ /* 2 initial groups, 1 reserved GDT block (at block 3) */
+ resize_configure_fs(test, 2, 1, 0, RESIZE_TEST_BLOCKS_PER_GRP);
+
+ /* 1. Overflow guards on blocks_count and s_inodes_count */
+ memset(&input, 0, sizeof(input));
+ input.group = 2;
+ input.blocks_count = 1024;
+ ext4_blocks_count_set(ctx->es, ~0ULL - 100);
+ KUNIT_EXPECT_EQ(test, ext4_group_add(sb, &input), -EINVAL);
+ ext4_blocks_count_set(ctx->es, 1 + 2 * RESIZE_TEST_BLOCKS_PER_GRP);
+
+ ctx->es->s_inodes_count = cpu_to_le32(~0U - 10);
+ KUNIT_EXPECT_EQ(test, ext4_group_add(sb, &input), -EINVAL);
+ ctx->es->s_inodes_count = cpu_to_le32(2 * RESIZE_TEST_INODES_PER_GRP);
+
+ /* 2. Add group 2 (no backup super, overhead = 0) */
+ start = ext4_blocks_count(ctx->es);
+ memset(&input, 0, sizeof(input));
+ input.group = 2;
+ input.blocks_count = RESIZE_TEST_BLOCKS_PER_GRP;
+ input.block_bitmap = start;
+ input.inode_bitmap = start + 1;
+ input.inode_table = start + 2;
+
+ KUNIT_EXPECT_EQ(test, ext4_group_add(sb, &input), 0);
+ KUNIT_EXPECT_EQ(test, ctx->sbi.s_groups_count, 3U);
+
+ /* 3. Add group 3 (sparse backup super -> overhead = 3, reserves backup) */
+ start = ext4_blocks_count(ctx->es);
+ memset(&input, 0, sizeof(input));
+ input.group = 3;
+ input.blocks_count = RESIZE_TEST_BLOCKS_PER_GRP;
+ input.block_bitmap = start + 3;
+ input.inode_bitmap = start + 4;
+ input.inode_table = start + 5;
+
+ /* Without resize_inode feature -> -EPERM */
+ ext4_clear_feature_resize_inode(sb);
+ KUNIT_EXPECT_EQ(test, ext4_group_add(sb, &input), -EPERM);
+ ext4_set_feature_resize_inode(sb);
+
+ rsv_iblocks_before = ctx->resize_inode->i_blocks;
+ KUNIT_EXPECT_EQ(test, ext4_group_add(sb, &input), 0);
+ KUNIT_EXPECT_EQ(test, ctx->sbi.s_groups_count, 4U);
+
+ /*
+ * reserve_backup_gdb() must append group 3's reserved GDT block
+ * (3 * 1024 + 3 = 3075) at index 1 (after group 1's backup) in
+ * primary reserved GDT block 3 and increment resize_inode->i_blocks.
+ */
+ pri_bh = resize_lookup_bh(ctx, 3);
+ KUNIT_ASSERT_NOT_NULL(test, pri_bh);
+ pri_data = (__le32 *)pri_bh->b_data;
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(pri_data[1]), 3U * 1024U + 3U);
+ KUNIT_EXPECT_EQ(test, ctx->resize_inode->i_blocks,
+ rsv_iblocks_before + (RESIZE_TEST_BLK_SIZE >> 9));
+}
+
+/*
+ * Test 7: ext4_convert_meta_bg() sanity validation of resize_inode and
+ * conversion from resize_inode to meta_bg.
+ */
+static void test_convert_meta_bg_validation_and_conversion(struct kunit *test)
+{
+ struct resize_test_ctx *ctx = test->priv;
+ struct super_block *sb = ctx->sb;
+ struct inode *rinode;
+ ext4_fsblk_t dind_blk = 42;
+
+ resize_configure_fs(test, 16, 0, 0, RESIZE_TEST_BLOCKS_PER_GRP);
+ ext4_set_feature_resize_inode(sb);
+
+ rinode = new_inode(sb);
+ KUNIT_ASSERT_NOT_NULL(test, rinode);
+ rinode->i_blkbits = RESIZE_TEST_BLK_BITS;
+ rinode->i_blocks = RESIZE_TEST_BLK_SIZE >> 9;
+ EXT4_I(rinode)->i_data[EXT4_DIND_BLOCK] = cpu_to_le32(dind_blk);
+
+ /* 1. Non-zero s_reserved_gdt_blocks -> -EPERM */
+ ctx->es->s_reserved_gdt_blocks = cpu_to_le16(1);
+ KUNIT_EXPECT_EQ(test, ext4_convert_meta_bg_test(sb, rinode), -EPERM);
+ ctx->es->s_reserved_gdt_blocks = 0;
+
+ /* 2. Wrong i_blocks on resize_inode -> -EINVAL */
+ rinode->i_blocks = 8;
+ KUNIT_EXPECT_EQ(test, ext4_convert_meta_bg_test(sb, rinode), -EINVAL);
+ rinode->i_blocks = RESIZE_TEST_BLK_SIZE >> 9;
+
+ /* 3. Unexpected direct block pointer or missing DIND -> -EINVAL */
+ EXT4_I(rinode)->i_data[0] = cpu_to_le32(10);
+ KUNIT_EXPECT_EQ(test, ext4_convert_meta_bg_test(sb, rinode), -EINVAL);
+ EXT4_I(rinode)->i_data[0] = 0;
+
+ EXT4_I(rinode)->i_data[EXT4_DIND_BLOCK] = 0;
+ KUNIT_EXPECT_EQ(test, ext4_convert_meta_bg_test(sb, rinode), -EINVAL);
+ EXT4_I(rinode)->i_data[EXT4_DIND_BLOCK] = cpu_to_le32(dind_blk);
+ ctx->sbi.s_mount_state &= ~EXT4_ERROR_FS;
+
+ /* 4. Valid conversion clears RESIZE_INODE, sets META_BG, frees DIND */
+ KUNIT_EXPECT_EQ(test, ext4_convert_meta_bg_test(sb, rinode), 0);
+ KUNIT_EXPECT_FALSE(test, ext4_has_feature_resize_inode(sb));
+ KUNIT_EXPECT_TRUE(test, ext4_has_feature_meta_bg(sb));
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(ctx->es->s_first_meta_bg), 1U);
+ KUNIT_EXPECT_EQ(test, ctx->last_freed_blk, dind_blk);
+ KUNIT_EXPECT_EQ(test, ctx->last_freed_count, 1UL);
+ KUNIT_EXPECT_EQ(test, rinode->i_blocks, 0ULL);
+ KUNIT_EXPECT_EQ(test, EXT4_I(rinode)->i_data[EXT4_DIND_BLOCK], 0);
+
+ iput(rinode);
+}
+
+/*
+ * Test 8: End-to-end ext4_resize_fs() across error guards, bigalloc cluster
+ * rounding, consuming reserved GDT blocks (16 -> 32 groups via add_new_gdb),
+ * converting resize_inode -> meta_bg, and growing into meta_bg (32 -> 34
+ * groups via add_new_gdb_meta_bg).
+ */
+static void test_resize_fs_across_resize_inode_to_meta_bg(struct kunit *test)
+{
+ struct resize_test_ctx *ctx = test->priv;
+ struct super_block *sb = ctx->sb;
+ ext4_fsblk_t o_blks, target_blks, dind_blk;
+
+ /*
+ * Start with 16 groups (1 full 64-byte GDT block), 1 reserved GDT
+ * block (allowing growth to 32 groups before meta_bg conversion), and
+ * flex_bg of 4 groups (log_flex = 2).
+ */
+ resize_configure_fs(test, 16, 1, 2, RESIZE_TEST_BLOCKS_PER_GRP);
+ o_blks = ext4_blocks_count(ctx->es);
+ dind_blk = le32_to_cpu(
+ EXT4_I(ctx->resize_inode)->i_data[EXT4_DIND_BLOCK]);
+
+ /* 1. Beyond device -> -ENOSPC; shrink -> -EINVAL; same size -> 0 */
+ KUNIT_EXPECT_EQ(test, ext4_resize_fs(sb, ctx->device_blocks + 100),
+ -ENOSPC);
+ KUNIT_EXPECT_EQ(test, ext4_resize_fs(sb, o_blks - 100), -EINVAL);
+ KUNIT_EXPECT_EQ(test, ext4_resize_fs(sb, o_blks), 0);
+
+ /* 2. Simultaneous resize_inode + meta_bg -> -EINVAL */
+ ext4_set_feature_meta_bg(sb);
+ KUNIT_EXPECT_EQ(test, ext4_resize_fs(sb, o_blks + 1024), -EINVAL);
+ ext4_clear_feature_meta_bg(sb);
+ ctx->sbi.s_mount_state &= ~EXT4_ERROR_FS;
+
+ /* 3. Grow from 16 groups to 34 groups across GDT & meta_bg boundary */
+ target_blks = RESIZE_TEST_FIRST_DATA_BLK +
+ 34ULL * RESIZE_TEST_BLOCKS_PER_GRP;
+ KUNIT_EXPECT_EQ(test, ext4_resize_fs(sb, target_blks), 0);
+
+ KUNIT_EXPECT_EQ(test, ctx->sbi.s_groups_count, 34U);
+ KUNIT_EXPECT_EQ(test, ctx->sbi.s_gdb_count, 3UL);
+ KUNIT_EXPECT_EQ(test, ext4_blocks_count(ctx->es), target_blks);
+ KUNIT_EXPECT_EQ(test, le16_to_cpu(ctx->es->s_reserved_gdt_blocks), 0U);
+ KUNIT_EXPECT_FALSE(test, ext4_has_feature_resize_inode(sb));
+ KUNIT_EXPECT_TRUE(test, ext4_has_feature_meta_bg(sb));
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(ctx->es->s_first_meta_bg), 2U);
+ KUNIT_EXPECT_EQ(test, ctx->last_freed_blk, dind_blk);
+ KUNIT_EXPECT_EQ(test, le32_to_cpu(ctx->es->s_inodes_count),
+ 34U * RESIZE_TEST_INODES_PER_GRP);
+}
+
+static struct kunit_case ext4_resize_test_cases[] = {
+ KUNIT_CASE(test_resize_begin_and_end_guards),
+ KUNIT_CASE(test_list_backups_and_verify_reserved_gdb),
+ KUNIT_CASE(test_verify_group_input_rejections_and_acceptance),
+ KUNIT_CASE(test_alloc_setup_flex_gd_and_group_tables),
+ KUNIT_CASE(test_group_extend_partial_last_group),
+ KUNIT_CASE(test_group_add_with_resize_inode_and_backup_gdb),
+ KUNIT_CASE(test_convert_meta_bg_validation_and_conversion),
+ KUNIT_CASE(test_resize_fs_across_resize_inode_to_meta_bg),
+ {}
+};
+
+static struct kunit_suite ext4_resize_test_suite = {
+ .name = "ext4_resize_test",
+ .init = resize_test_init,
+ .test_cases = ext4_resize_test_cases,
+};
+
+kunit_test_suites(&ext4_resize_test_suite);
+
+MODULE_DESCRIPTION("KUnit tests for ext4 online filesystem resizing");
+MODULE_LICENSE("GPL");
diff --git a/fs/ext4/resize.c b/fs/ext4/resize.c
index 2c5b851c552a..e873e09e609a 100644
--- a/fs/ext4/resize.c
+++ b/fs/ext4/resize.c
@@ -13,6 +13,7 @@
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
+#include <kunit/static_stub.h>
#include "ext4_jbd2.h"
@@ -42,6 +43,16 @@ void ext4_kvfree_array_rcu(void *to_free)
synchronize_rcu();
kvfree(to_free);
}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_kvfree_array_rcu);
+
+int ext4_resize_issue_zeroout(struct super_block *sb, sector_t blk,
+ sector_t nr_blks, gfp_t gfp_mask)
+{
+ KUNIT_STATIC_STUB_REDIRECT(ext4_resize_issue_zeroout, sb, blk,
+ nr_blks, gfp_mask);
+ return sb_issue_zeroout(sb, blk, nr_blks, gfp_mask);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_resize_issue_zeroout);
int ext4_resize_begin(struct super_block *sb)
{
@@ -94,6 +105,7 @@ int ext4_resize_begin(struct super_block *sb)
return ret;
}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_resize_begin);
int ext4_resize_end(struct super_block *sb, bool update_backups)
{
@@ -103,6 +115,7 @@ int ext4_resize_end(struct super_block *sb, bool update_backups)
return ext4_update_overhead(sb, true);
return 0;
}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_resize_end);
static ext4_grpblk_t ext4_group_overhead_blocks(struct super_block *sb,
ext4_group_t group) {
@@ -209,21 +222,6 @@ static int verify_group_input(struct super_block *sb,
return err;
}
-/*
- * ext4_new_flex_group_data is used by 64bit-resize interface to add a flex
- * group each time.
- */
-struct ext4_new_flex_group_data {
- struct ext4_new_group_data *groups; /* new_group_data for groups
- in the flex group */
- __u16 *bg_flags; /* block group flags of groups
- in @groups */
- ext4_group_t resize_bg; /* number of allocated
- new_group_data */
- ext4_group_t count; /* number of groups in @groups
- */
-};
-
/*
* Avoiding memory allocation failures due to too many groups added each time.
*/
@@ -425,7 +423,7 @@ static struct buffer_head *bclean(handle_t *handle, struct super_block *sb,
struct buffer_head *bh;
int err;
- bh = sb_getblk(sb, blk);
+ bh = ext4_sb_getblk(sb, blk);
if (unlikely(!bh))
return ERR_PTR(-ENOMEM);
BUFFER_TRACE(bh, "get_write_access");
@@ -489,7 +487,7 @@ static int set_flexbg_block_bitmap(struct super_block *sb, handle_t *handle,
if (err < 0)
return err;
- bh = sb_getblk(sb, flex_gd->groups[group].block_bitmap);
+ bh = ext4_sb_getblk(sb, flex_gd->groups[group].block_bitmap);
if (unlikely(!bh))
return -ENOMEM;
@@ -578,7 +576,7 @@ static int setup_new_flex_group_blocks(struct super_block *sb,
if (err < 0)
goto out;
- gdb = sb_getblk(sb, block);
+ gdb = ext4_sb_getblk(sb, block);
if (unlikely(!gdb)) {
err = -ENOMEM;
goto out;
@@ -607,8 +605,8 @@ static int setup_new_flex_group_blocks(struct super_block *sb,
* table blocks
*/
if (ext4_bg_has_super(sb, group)) {
- err = sb_issue_zeroout(sb, gdblocks + start + 1,
- reserved_gdb, GFP_NOFS);
+ err = ext4_resize_issue_zeroout(sb, gdblocks + start + 1,
+ reserved_gdb, GFP_NOFS);
if (err)
goto out;
}
@@ -622,8 +620,8 @@ static int setup_new_flex_group_blocks(struct super_block *sb,
block = group_data[i].inode_table;
ext4_debug("clear inode table blocks %#04llx -> %#04lx\n",
block, sbi->s_itb_per_group);
- err = sb_issue_zeroout(sb, block, sbi->s_itb_per_group,
- GFP_NOFS);
+ err = ext4_resize_issue_zeroout(sb, block, sbi->s_itb_per_group,
+ GFP_NOFS);
if (err)
goto out;
@@ -767,6 +765,7 @@ unsigned int ext4_list_backups(struct super_block *sb, unsigned int *three,
return ret;
}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_list_backups);
/*
* Check that all of the backup GDT blocks are held in the primary GDT block.
@@ -1182,7 +1181,7 @@ static void update_backups(struct super_block *sb, sector_t blk_off, char *data,
else
backup_block = first_block + has_super;
- bh = sb_getblk(sb, backup_block);
+ bh = ext4_sb_getblk(sb, backup_block);
if (unlikely(!bh)) {
err = -ENOMEM;
break;
@@ -1295,7 +1294,7 @@ static int ext4_add_new_descs(handle_t *handle, struct super_block *sb,
static struct buffer_head *ext4_get_bitmap(struct super_block *sb, __u64 block)
{
- struct buffer_head *bh = sb_getblk(sb, block);
+ struct buffer_head *bh = ext4_sb_getblk(sb, block);
if (unlikely(!bh))
return NULL;
if (!bh_uptodate_or_lock(bh)) {
@@ -1763,6 +1762,7 @@ int ext4_group_add(struct super_block *sb, struct ext4_new_group_data *input)
iput(inode);
return err;
} /* ext4_group_add */
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_group_add);
/*
* extend a group without checking assuming that checking has been done.
@@ -1894,6 +1894,7 @@ int ext4_group_extend(struct super_block *sb, struct ext4_super_block *es,
return ext4_group_extend_no_check(sb, o_blocks_count, add);
} /* ext4_group_extend */
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_group_extend);
static int num_desc_blocks(struct super_block *sb, ext4_group_t groups)
@@ -2190,3 +2191,56 @@ int ext4_resize_fs(struct super_block *sb, ext4_fsblk_t n_blocks_count)
ext4_blocks_count(es));
return err;
}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_resize_fs);
+
+#if IS_ENABLED(CONFIG_EXT4_KUNIT_TESTS)
+int ext4_verify_group_input_test(struct super_block *sb,
+ struct ext4_new_group_data *input)
+{
+ return verify_group_input(sb, input);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_verify_group_input_test);
+
+struct ext4_new_flex_group_data *
+ext4_alloc_flex_gd_test(unsigned int flexbg_size, ext4_group_t o_group,
+ ext4_group_t n_group)
+{
+ return alloc_flex_gd(flexbg_size, o_group, n_group);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_alloc_flex_gd_test);
+
+void ext4_free_flex_gd_test(struct ext4_new_flex_group_data *flex_gd)
+{
+ free_flex_gd(flex_gd);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_free_flex_gd_test);
+
+int ext4_alloc_group_tables_test(struct super_block *sb,
+ struct ext4_new_flex_group_data *flex_gd,
+ unsigned int flexbg_size)
+{
+ return ext4_alloc_group_tables(sb, flex_gd, flexbg_size);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_alloc_group_tables_test);
+
+int ext4_verify_reserved_gdb_test(struct super_block *sb, ext4_group_t end,
+ struct buffer_head *primary)
+{
+ return verify_reserved_gdb(sb, end, primary);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_verify_reserved_gdb_test);
+
+int ext4_setup_next_flex_gd_test(struct super_block *sb,
+ struct ext4_new_flex_group_data *flex_gd,
+ ext4_fsblk_t n_blocks_count)
+{
+ return ext4_setup_next_flex_gd(sb, flex_gd, n_blocks_count);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_setup_next_flex_gd_test);
+
+int ext4_convert_meta_bg_test(struct super_block *sb, struct inode *inode)
+{
+ return ext4_convert_meta_bg(sb, inode);
+}
+EXPORT_SYMBOL_FOR_EXT4_TEST(ext4_convert_meta_bg_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 ` Theodore Ts'o [this message]
2026-11-12 4:34 ` [PATCH 20/27] ext4: add KUnit tests for fs/ext4/resize.c 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-21-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