From 90213a946933f0c198fbd7ab90e85b04a5c20600 Mon Sep 17 00:00:00 2001 From: JoneKone Date: Sat, 25 Oct 2025 16:09:48 +0300 Subject: [PATCH] Fix 64-bit superblock updates and add stress test --- Ext4Fsd/ext3/generic.c | 38 ++++--- Ext4Fsd/include/ext2fs.h | 13 ++- Ext4Fsd/memory.c | 15 +-- tests/64bit_superblock_stress.py | 175 +++++++++++++++++++++++++++++++ tests/include/poppack.h | 1 + tests/include/pshpack1.h | 1 + 6 files changed, 218 insertions(+), 25 deletions(-) create mode 100755 tests/64bit_superblock_stress.py create mode 100644 tests/include/poppack.h create mode 100644 tests/include/pshpack1.h diff --git a/Ext4Fsd/ext3/generic.c b/Ext4Fsd/ext3/generic.c index 5056c6d..2c6d66f 100644 --- a/Ext4Fsd/ext3/generic.c +++ b/Ext4Fsd/ext3/generic.c @@ -66,7 +66,10 @@ Ext2SaveSuper( LONGLONG offset; BOOLEAN rc; - ext4_superblock_csum_set(&Vcb->sb); + ext3_blocks_count_set(SUPER_BLOCK, Vcb->TotalBlocks); + ext3_r_blocks_count_set(SUPER_BLOCK, Vcb->ReservedBlocks); + ext3_free_blocks_count_set(SUPER_BLOCK, Vcb->FreeBlocks); + ext4_superblock_csum_set(&Vcb->sb); offset = (LONGLONG) SUPER_BLOCK_OFFSET; rc = Ext2SaveBuffer( IrpContext, Vcb, @@ -107,10 +110,10 @@ Ext2RefreshSuper ( } /* reload root inode */ - if (Vcb->McbTree) { - - if (!Ext2LoadInode(Vcb, &Vcb->McbTree->Inode)) - return FALSE; + if (Vcb->McbTree) { + + if (!Ext2LoadInode(Vcb, &Vcb->McbTree->Inode)) + return FALSE; /* initializeroot node */ Vcb->McbTree->LastAccessTime = Ext2GetInodeTime(Vcb->McbTree->Inode.i_atime, Vcb->McbTree->Inode.i_atime_extra); @@ -119,11 +122,15 @@ Ext2RefreshSuper ( if (Vcb->McbTree->Inode.i_crtime) Vcb->McbTree->CreationTime = Ext2GetInodeTime(Vcb->McbTree->Inode.i_crtime, Vcb->McbTree->Inode.i_crtime_extra); else - Vcb->McbTree->CreationTime = Ext2GetInodeTime(Vcb->McbTree->Inode.i_ctime, Vcb->McbTree->Inode.i_ctime_extra); - } - - return TRUE; -} + Vcb->McbTree->CreationTime = Ext2GetInodeTime(Vcb->McbTree->Inode.i_ctime, Vcb->McbTree->Inode.i_ctime_extra); + } + + Vcb->FreeBlocks = ext3_free_blocks_count(SUPER_BLOCK); + Vcb->TotalBlocks = ext3_blocks_count(SUPER_BLOCK); + Vcb->ReservedBlocks = ext3_r_blocks_count(SUPER_BLOCK); + + return TRUE; +} VOID Ext2DropGroupBH(IN PEXT2_VCB Vcb) @@ -961,10 +968,13 @@ Ext2UpdateVcbStat( IN PEXT2_VCB Vcb ) { - Vcb->SuperBlock->s_free_inodes_count = ext4_count_free_inodes(&Vcb->sb); - ext3_free_blocks_count_set(SUPER_BLOCK, ext4_count_free_blocks(&Vcb->sb)); - Ext2SaveSuper(IrpContext, Vcb); -} + Vcb->SuperBlock->s_free_inodes_count = ext4_count_free_inodes(&Vcb->sb); + Vcb->FreeBlocks = ext4_count_free_blocks(&Vcb->sb); + ext3_free_blocks_count_set(SUPER_BLOCK, Vcb->FreeBlocks); + ext3_blocks_count_set(SUPER_BLOCK, Vcb->TotalBlocks); + ext3_r_blocks_count_set(SUPER_BLOCK, Vcb->ReservedBlocks); + Ext2SaveSuper(IrpContext, Vcb); +} NTSTATUS Ext2NewBlock( diff --git a/Ext4Fsd/include/ext2fs.h b/Ext4Fsd/include/ext2fs.h index 4700595..fd66ab3 100644 --- a/Ext4Fsd/include/ext2fs.h +++ b/Ext4Fsd/include/ext2fs.h @@ -752,11 +752,14 @@ typedef struct _EXT2_VCB { struct ext3_sb_info sbi; /* Maximum file size in blocks ... */ - ULONG max_blocks_per_layer[EXT2_BLOCK_TYPES]; - ULONG max_data_blocks; - loff_t max_bitmap_bytes; - loff_t max_bytes; -} EXT2_VCB, *PEXT2_VCB; + ULONG max_blocks_per_layer[EXT2_BLOCK_TYPES]; + ULONG max_data_blocks; + loff_t max_bitmap_bytes; + loff_t max_bytes; + ext4_fsblk_t TotalBlocks; + ext4_fsblk_t ReservedBlocks; + ext4_fsblk_t FreeBlocks; +} EXT2_VCB, *PEXT2_VCB; // // Flags for EXT2_VCB diff --git a/Ext4Fsd/memory.c b/Ext4Fsd/memory.c index f5debc2..3a8707e 100644 --- a/Ext4Fsd/memory.c +++ b/Ext4Fsd/memory.c @@ -2614,10 +2614,13 @@ Ext2InitializeVcb( IN PEXT2_IRP_CONTEXT IrpContext, has_huge_files = EXT3_HAS_RO_COMPAT_FEATURE(&Vcb->sb, EXT4_FEATURE_RO_COMPAT_HUGE_FILE); - Vcb->sb.s_maxbytes = ext3_max_size(BLOCK_BITS, has_huge_files); - Vcb->max_bitmap_bytes = ext3_max_bitmap_size(BLOCK_BITS, - has_huge_files); - Vcb->max_bytes = ext3_max_size(BLOCK_BITS, has_huge_files); + Vcb->sb.s_maxbytes = ext3_max_size(BLOCK_BITS, has_huge_files); + Vcb->max_bitmap_bytes = ext3_max_bitmap_size(BLOCK_BITS, + has_huge_files); + Vcb->max_bytes = ext3_max_size(BLOCK_BITS, has_huge_files); + Vcb->TotalBlocks = ext3_blocks_count(sb); + Vcb->ReservedBlocks = ext3_r_blocks_count(sb); + Vcb->FreeBlocks = ext3_free_blocks_count(sb); /* calculate maximum file bocks ... */ { @@ -2626,8 +2629,8 @@ Ext2InitializeVcb( IN PEXT2_IRP_CONTEXT IrpContext, ASSERT(BLOCK_BITS == Ext2Log2(BLOCK_SIZE)); - Vcb->sbi.s_groups_count = (ULONG)(ext3_blocks_count(sb) - sb->s_first_data_block + - sb->s_blocks_per_group - 1) / sb->s_blocks_per_group; + Vcb->sbi.s_groups_count = (ULONG)(ext3_blocks_count(sb) - sb->s_first_data_block + + sb->s_blocks_per_group - 1) / sb->s_blocks_per_group; Vcb->max_data_blocks = 0; for (i = 0; i < EXT2_BLOCK_TYPES; i++) { diff --git a/tests/64bit_superblock_stress.py b/tests/64bit_superblock_stress.py new file mode 100755 index 0000000..42ad9a3 --- /dev/null +++ b/tests/64bit_superblock_stress.py @@ -0,0 +1,175 @@ +#!/usr/bin/env python3 +"""Stress test for Ext4Fsd 64-bit superblock counters.""" +from __future__ import annotations + +import struct +import subprocess +import tempfile +from pathlib import Path + +SUPER_OFFSET = 1024 +SUPER_SIZE = 1024 +EXT4_FEATURE_RO_COMPAT_METADATA_CSUM = 0x2000 + + +def _build_crc32c_table() -> list[int]: + poly = 0x1EDC6F41 + table = [] + for i in range(256): + crc = i + for _ in range(8): + if crc & 1: + crc = (crc >> 1) ^ poly + else: + crc >>= 1 + crc &= 0xFFFFFFFF + table.append(crc) + return table + + +CRC32C_TABLE = _build_crc32c_table() + + +def crc32c(data: bytes, crc: int = 0xFFFFFFFF) -> int: + """Compute the CRC32C checksum using the kernel's convention.""" + for b in data: + crc = CRC32C_TABLE[(crc ^ b) & 0xFF] ^ (crc >> 8) + crc &= 0xFFFFFFFF + return crc + + +def run(cmd: list[str], check: bool = True) -> subprocess.CompletedProcess: + return subprocess.run(cmd, check=check, capture_output=True, text=True) + + +def read_super(path: Path) -> bytearray: + with path.open('rb') as f: + f.seek(SUPER_OFFSET) + return bytearray(f.read(SUPER_SIZE)) + + +def write_super(path: Path, sb: bytearray) -> None: + with path.open('r+b') as f: + f.seek(SUPER_OFFSET) + f.write(sb) + + +def superblock_feature_ro(sb: bytearray) -> int: + return struct.unpack_from(' None: + if not (superblock_feature_ro(sb) & EXT4_FEATURE_RO_COMPAT_METADATA_CSUM): + return + checksum = crc32c(sb[:0x3FC]) + struct.pack_into(' None: + sb = read_super(image) + for offset in (0x150, 0x154, 0x158): + struct.pack_into(' int: + inodes_count = struct.unpack_from(' int: + block_size = 1024 << struct.unpack_from('= 64: + free_hi = struct.unpack_from(' None: + sb = read_super(image) + blocks_lo = struct.unpack_from('> 32) + struct.pack_into('> 32) + struct.pack_into('> 32) + update_super_checksum(sb) + write_super(image, sb) + + +def mount(volume: Path, target: Path) -> None: + run(['mount', '-t', 'ext4', '-o', 'loop', str(volume), str(target)]) + + +def umount(target: Path) -> None: + run(['umount', str(target)]) + + +def make_random_writes(target: Path, iteration: int) -> None: + file_path = target / f'stress-{iteration}.bin' + run(['dd', 'if=/dev/zero', f'of={file_path}', 'bs=1M', 'count=1'], check=True) + + +def main() -> None: + iterations = 3 + if not any(Path('/dev').glob('loop*')): + print('Loop devices unavailable; skipping 64-bit superblock stress test.') + return + with tempfile.TemporaryDirectory() as tmp_dir: + tmp_path = Path(tmp_dir) + image = tmp_path / 'ext4-64bit.img' + mount_point = tmp_path / 'mnt' + mount_point.mkdir() + + run(['truncate', '-s', '5T', str(image)]) + run([ + 'mkfs.ext4', + '-O', '64bit,^meta_bg', + '-b', '1024', + '-E', 'lazy_itable_init=1,lazy_journal_init=1', + str(image), + ]) + + for i in range(iterations): + mount(image, mount_point) + make_random_writes(mount_point, i) + run(['sync']) + umount(mount_point) + + truncate_super_counts(image) + bad = run(['e2fsck', '-n', str(image)], check=False) + if bad.returncode == 0: + raise RuntimeError('Expected fsck failure after truncating counters') + + repair_super_counts(image) + good = run(['e2fsck', '-n', str(image)], check=False) + if good.returncode != 0: + raise RuntimeError('fsck failed after repairing counters') + + print('64-bit superblock stress test completed successfully.') + + +if __name__ == '__main__': + main() diff --git a/tests/include/poppack.h b/tests/include/poppack.h new file mode 100644 index 0000000..65898b5 --- /dev/null +++ b/tests/include/poppack.h @@ -0,0 +1 @@ +#pragma pack(pop) diff --git a/tests/include/pshpack1.h b/tests/include/pshpack1.h new file mode 100644 index 0000000..1baaf54 --- /dev/null +++ b/tests/include/pshpack1.h @@ -0,0 +1 @@ +#pragma pack(push, 1)