use super::*;
use crate::{
BLOCK_SIZE, bmalloc::InodeNumber, disknode::Ext4Inode, endian::DiskFormat,
entries::Ext4DirEntryTail, error::Errno, superblock::Ext4Superblock,
};
fn metadata_csum_superblock() -> Ext4Superblock {
let mut sb = Ext4Superblock::default();
sb.s_magic = Ext4Superblock::EXT4_SUPER_MAGIC;
sb.s_feature_ro_compat |= Ext4Superblock::EXT4_FEATURE_RO_COMPAT_METADATA_CSUM;
sb.s_inode_size = Ext4Inode::LARGE_INODE_SIZE;
sb.s_clusters_per_group = 8192;
sb.s_inodes_per_group = 2048;
sb.s_blocks_count_lo = 1024;
sb.s_free_blocks_count_lo = 900;
sb.s_free_inodes_count = 2000;
sb.s_uuid = [
0x5A, 0xC3, 0x11, 0x7E, 0x90, 0xAB, 0x4D, 0x2F, 0x10, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
0x88,
];
sb
}
fn sample_inode() -> Ext4Inode {
let mut inode = Ext4Inode {
i_mode: Ext4Inode::S_IFREG | 0o764,
i_uid: 0x1234,
i_size_lo: 0x5566_7788,
i_atime: 100,
i_ctime: 200,
i_mtime: 300,
i_dtime: 0,
i_gid: 0x5678,
i_links_count: 2,
i_blocks_lo: 16,
i_flags: Ext4Inode::EXT4_EXTENTS_FL,
l_i_version: 7,
i_generation: 0xCAFE_BABE,
i_size_high: 1,
l_i_blocks_high: 2,
l_i_uid_high: 0x9ABC,
l_i_gid_high: 0xDEF0,
i_extra_isize: Ext4Inode::required_extra_isize(Ext4Inode::FIELD_END_I_PROJID),
i_crtime: 400,
i_projid: 123,
..Default::default()
};
inode.write_extend_header();
inode
}
#[test]
fn superblock_checksum_round_trips_and_corruption_returns_euclean() {
let mut sb = metadata_csum_superblock();
sb.update_checksum();
let verified = sb.verify_superblock().unwrap();
assert_eq!(verified.s_checksum, sb.s_checksum);
let mut bytes = [0u8; Ext4Superblock::SUPERBLOCK_SIZE];
sb.to_disk_bytes(&mut bytes);
let stored = u32::from_le_bytes(
bytes[Ext4Superblock::SUPERBLOCK_SIZE - 4..]
.try_into()
.unwrap(),
);
assert_eq!(stored, sb.s_checksum);
let mut corrupted = sb;
corrupted.s_free_blocks_count_lo ^= 1;
let err = corrupted.verify_superblock().unwrap_err();
assert_eq!(err.code, Errno::EUCLEAN);
}
#[test]
fn inode_checksum_is_split_and_persisted_to_disk() {
let sb = metadata_csum_superblock();
let inode_num = InodeNumber::new(42).unwrap();
let generation = 0x1020_3040;
let inode_size = Ext4Inode::LARGE_INODE_SIZE as usize;
let mut inode = sample_inode();
inode.i_generation = generation;
ext4_update_inode_checksum(&sb, inode_num, generation, &mut inode, inode_size);
let expected = ext4_inode_csum32(&sb, inode_num, generation, &inode, inode_size);
assert_eq!(inode.l_i_checksum_lo, (expected & 0xFFFF) as u16);
assert_eq!(inode.i_checksum_hi, ((expected >> 16) & 0xFFFF) as u16);
let mut bytes = [0u8; Ext4Inode::LARGE_INODE_SIZE as usize];
inode.to_disk_bytes(&mut bytes);
assert_eq!(
u16::from_le_bytes(bytes[124..126].try_into().unwrap()),
inode.l_i_checksum_lo
);
assert_eq!(
u16::from_le_bytes(bytes[130..132].try_into().unwrap()),
inode.i_checksum_hi
);
}
#[test]
fn dirblock_checksum_is_stored_and_detects_corruption() {
let sb = metadata_csum_superblock();
let ino = 11;
let generation = 0x5566_7788;
let mut block = [0u8; BLOCK_SIZE];
let tail_offset = BLOCK_SIZE - Ext4DirEntryTail::TAIL_LEN as usize;
block[..12].copy_from_slice(b"hello crc32c");
Ext4DirEntryTail::new().to_disk_bytes(&mut block[tail_offset..tail_offset + 12]);
update_ext4_dirblock_csum32(&sb, ino, generation, &mut block);
let stored = u32::from_le_bytes(block[BLOCK_SIZE - 4..].try_into().unwrap());
let expected = ext4_dirblock_csum32(&sb, ino, generation, &block[..tail_offset]);
assert_eq!(stored, expected);
assert!(verify_ext4_dirblock_checksum(&sb, ino, generation, &block));
block[0] ^= 0x80;
assert!(!verify_ext4_dirblock_checksum(&sb, ino, generation, &block));
}