use super::core::ext4_metadata_csum32;
use crate::{
BLOCK_SIZE,
crc32c::{ext4_crc32c_seed_from_superblock, ext4_superblock_has_metadata_csum},
endian::read_u16_le,
entries::{Ext4DirEntryTail, Ext4DxEntry, Ext4DxRootInfo},
superblock::Ext4Superblock,
};
pub fn ext4_metadata_block_csum32(sb: &Ext4Superblock, data: &[u8]) -> u32 {
let seed = ext4_crc32c_seed_from_superblock(sb);
ext4_metadata_csum32(seed, &[data])
}
pub fn verify_ext4_dirblock_checksum(
sb: &Ext4Superblock,
ino: u32,
generation: u32,
block_bytes: &[u8],
) -> bool {
if !ext4_superblock_has_metadata_csum(sb) {
return true;
}
if block_bytes.len() < BLOCK_SIZE || BLOCK_SIZE < 12 {
return false;
}
let tail_ft = block_bytes[BLOCK_SIZE - 5];
if tail_ft != 0xDE {
return true;
}
let stored = u32::from_le_bytes([
block_bytes[BLOCK_SIZE - 4],
block_bytes[BLOCK_SIZE - 3],
block_bytes[BLOCK_SIZE - 2],
block_bytes[BLOCK_SIZE - 1],
]);
let data_len = BLOCK_SIZE - Ext4DirEntryTail::TAIL_LEN as usize;
let computed = ext4_dirblock_csum32(sb, ino, generation, &block_bytes[..data_len]);
computed == stored
}
pub fn update_ext4_dirblock_csum32(
sb: &Ext4Superblock,
parent_dir_ino: u32,
generation: u32,
block_bytes: &mut [u8],
) {
if ext4_superblock_has_metadata_csum(sb) {
let data_len = BLOCK_SIZE - Ext4DirEntryTail::TAIL_LEN as usize;
let checksum =
ext4_dirblock_csum32(sb, parent_dir_ino, generation, &block_bytes[..data_len]);
block_bytes[BLOCK_SIZE - 4..].copy_from_slice(&checksum.to_le_bytes());
}
}
pub fn ext4_dirblock_csum32(
sb: &Ext4Superblock,
ino: u32,
generation: u32,
block_bytes: &[u8],
) -> u32 {
let seed = ext4_crc32c_seed_from_superblock(sb);
let ino_le = ino.to_le_bytes();
let generation_le = generation.to_le_bytes();
ext4_metadata_csum32(seed, &[&ino_le, &generation_le, block_bytes])
}
pub fn ext4_update_dirblock_tail_checksum(
sb: &Ext4Superblock,
ino: u32,
generation: u32,
block_bytes: &mut [u8],
tail_offset: usize,
) {
if tail_offset + 12 > block_bytes.len() {
return;
}
block_bytes[tail_offset + 8..tail_offset + 12].fill(0);
let checksum = ext4_dirblock_csum32(sb, ino, generation, &block_bytes[..tail_offset]);
block_bytes[tail_offset + 8..tail_offset + 12].copy_from_slice(&checksum.to_le_bytes());
}
pub fn verify_ext4_dx_checksum(
sb: &Ext4Superblock,
ino: u32,
generation: u32,
block_bytes: &[u8],
) -> Option<bool> {
if !ext4_superblock_has_metadata_csum(sb) {
return Some(true);
}
if block_bytes.len() < BLOCK_SIZE {
return Some(false);
}
let count_offset = dx_countlimit_offset(block_bytes)?;
if count_offset + 4 > BLOCK_SIZE {
return Some(false);
}
let limit = read_u16_le(&block_bytes[count_offset..count_offset + 2]) as usize;
let count = read_u16_le(&block_bytes[count_offset + 2..count_offset + 4]) as usize;
let entry_size = core::mem::size_of::<Ext4DxEntry>();
let tail_len = core::mem::size_of::<u64>();
if count > limit || count_offset + limit.saturating_mul(entry_size) > BLOCK_SIZE - tail_len {
return Some(false);
}
let tail_offset = count_offset + limit * entry_size;
if tail_offset + tail_len > BLOCK_SIZE {
return Some(false);
}
let data_len = count_offset + count * entry_size;
if data_len > tail_offset {
return Some(false);
}
let stored = u32::from_le_bytes([
block_bytes[tail_offset + 4],
block_bytes[tail_offset + 5],
block_bytes[tail_offset + 6],
block_bytes[tail_offset + 7],
]);
let zero_checksum = [0u8; 4];
let seed = ext4_crc32c_seed_from_superblock(sb);
let ino_le = ino.to_le_bytes();
let generation_le = generation.to_le_bytes();
let computed = ext4_metadata_csum32(
seed,
&[
&ino_le,
&generation_le,
&block_bytes[..data_len],
&block_bytes[tail_offset..tail_offset + 4],
&zero_checksum,
],
);
Some(computed == stored)
}
fn dx_countlimit_offset(block_bytes: &[u8]) -> Option<usize> {
let rec_len = read_u16_le(&block_bytes[4..6]) as usize;
if rec_len == BLOCK_SIZE {
return Some(8);
}
if rec_len != 12 || BLOCK_SIZE < 32 {
return None;
}
let root_info_offset = 24;
let reserved_zero = u32::from_le_bytes([
block_bytes[root_info_offset],
block_bytes[root_info_offset + 1],
block_bytes[root_info_offset + 2],
block_bytes[root_info_offset + 3],
]);
let info_length = block_bytes[root_info_offset + 5];
if reserved_zero != 0 || info_length != Ext4DxRootInfo::INFO_LENGTH {
return None;
}
Some(32)
}