use byteorder::{BigEndian, ByteOrder};
#[cfg(feature = "rust173-polyfill")]
use disk_core::rust173::IntIsMultipleOf as _;
use super::{hfs_issue, HfsFsckCode};
use crate::hfs::HfsMasterDirectoryBlock;
use disk_core::fsck::FsckIssue;
pub(super) fn validate_hfs_name(name: &[u8]) -> Option<String> {
if name.is_empty() {
return Some("name is empty".into());
}
if name.len() > 31 {
return Some(format!(
"name length {} exceeds HFS maximum of 31",
name.len()
));
}
if name.contains(&0x3A) {
return Some("name contains colon (HFS path separator)".into());
}
None
}
pub(super) fn unusual_hfs_name(name: &[u8]) -> Option<String> {
if name.contains(&0x00) {
return Some("name contains a null byte (valid on classic HFS, but unusual)".into());
}
None
}
pub(super) fn check_allocation_area_end(
mdb: &HfsMasterDirectoryBlock,
partition_len: Option<u64>,
errors: &mut Vec<FsckIssue>,
) {
let Some(len) = partition_len.filter(|&l| l >= 2048) else {
return;
};
#[allow(clippy::manual_is_multiple_of)]
if mdb.block_size == 0 || mdb.block_size % 512 != 0 || mdb.total_blocks == 0 {
return;
}
let spb = mdb.block_size as u64 / 512;
let last = len / 512 - 2;
if mdb.first_alloc_block as u64 >= last {
return;
}
let expected = (last - mdb.first_alloc_block as u64) / spb;
let have = mdb.total_blocks as u64;
if have == expected {
return;
}
let what = if have < expected {
format!(
"the volume stops {} bytes short of its {len}-byte partition; grow it with \
`resize --size {len}` (or format one at the partition size when its volume \
bitmap has no room)",
(expected - have) * mdb.block_size as u64
)
} else {
format!(
"the volume overruns its {len}-byte partition by {} bytes",
(have - expected) * mdb.block_size as u64
)
};
errors.push(hfs_issue(
HfsFsckCode::AllocationAreaEnd,
format!(
"{have} allocation blocks, but the partition holds {expected} between drAlBlSt \
and its alternate MDB: {what}"
),
));
}
pub(super) fn check_alternate_mdb(
mdb: &HfsMasterDirectoryBlock,
alt_sector: &[u8; 512],
errors: &mut Vec<FsckIssue>,
) {
let alt_sig = BigEndian::read_u16(&alt_sector[0..2]);
if alt_sig != 0x4244 {
return;
}
#[allow(clippy::type_complexity)]
let checks: &[(&str, fn(&[u8; 512]) -> u32, u32)] = &[
(
"block size",
|s| BigEndian::read_u32(&s[20..24]),
mdb.block_size,
),
(
"total blocks",
|s| BigEndian::read_u16(&s[18..20]) as u32,
mdb.total_blocks as u32,
),
(
"volume bitmap start",
|s| BigEndian::read_u16(&s[14..16]) as u32,
mdb.volume_bitmap_block as u32,
),
(
"first alloc block start",
|s| BigEndian::read_u16(&s[28..30]) as u32,
mdb.first_alloc_block as u32,
),
];
for (field, read_fn, primary_val) in checks {
let alt_val = read_fn(alt_sector);
if alt_val != *primary_val {
errors.push(hfs_issue(
HfsFsckCode::AlternateMdbMismatch,
format!(
"alternate MDB {} ({}) differs from primary ({})",
field, alt_val, primary_val
),
));
}
}
}
pub(super) fn check_embedded_hfs_plus(
mdb: &HfsMasterDirectoryBlock,
errors: &mut Vec<FsckIssue>,
warnings: &mut Vec<FsckIssue>,
) {
if mdb.embedded_signature == 0 {
return; }
if mdb.embedded_signature == 0x482B {
let start = mdb.embedded_start_block as u32;
let count = mdb.embedded_block_count as u32;
if start + count > mdb.total_blocks as u32 {
errors.push(hfs_issue(
HfsFsckCode::EmbeddedHfsPlusInvalid,
format!(
"embedded HFS+ region [{}, +{}) exceeds total blocks {}",
start, count, mdb.total_blocks
),
));
} else {
warnings.push(hfs_issue(
HfsFsckCode::EmbeddedHfsPlusInvalid,
format!(
"HFS wrapper contains embedded HFS+ volume (blocks {}..{})",
start,
start + count
),
));
}
} else {
errors.push(hfs_issue(
HfsFsckCode::EmbeddedHfsPlusInvalid,
format!(
"unknown embedded volume signature 0x{:04X}",
mdb.embedded_signature
),
));
}
}
pub(super) fn check_mdb(mdb: &HfsMasterDirectoryBlock, errors: &mut Vec<FsckIssue>) {
if mdb.signature != 0x4244 {
errors.push(hfs_issue(
HfsFsckCode::BadSignature,
format!(
"bad MDB signature: 0x{:04X} (expected 0x4244)",
mdb.signature
),
));
}
#[allow(clippy::manual_is_multiple_of)]
if mdb.block_size == 0 || mdb.block_size % 512 != 0 {
errors.push(hfs_issue(
HfsFsckCode::BadBlockSize,
format!(
"bad allocation block size: {} (must be a positive multiple of 512)",
mdb.block_size
),
));
}
if mdb.block_size > 0x7FFF_FE00 {
errors.push(hfs_issue(
HfsFsckCode::BlockSizeUpperBound,
format!(
"allocation block size 0x{:08X} exceeds HFS maximum (0x7FFFFE00)",
mdb.block_size
),
));
}
if mdb.total_blocks == 0 && mdb.block_size > 0 {
errors.push(hfs_issue(
HfsFsckCode::MdbTotalBlocksInvalid,
"total allocation blocks is 0".to_string(),
));
}
if mdb.volume_bitmap_block < 3 {
errors.push(hfs_issue(
HfsFsckCode::MdbVBMStTooLow,
format!(
"volume bitmap start sector {} is too low (must be >= 3)",
mdb.volume_bitmap_block
),
));
}
if mdb.block_size > 0 && mdb.total_blocks > 0 {
let bits_per_sector: u32 = 512 * 8; let vbm_sectors = (mdb.total_blocks as u32).div_ceil(bits_per_sector);
let vbm_end = mdb.volume_bitmap_block as u32 + vbm_sectors;
if vbm_end > mdb.first_alloc_block as u32 {
errors.push(hfs_issue(
HfsFsckCode::VbmDataAreaCollision,
format!(
"volume bitmap (sectors {}..{}) overlaps data area (starts at sector {})",
mdb.volume_bitmap_block, vbm_end, mdb.first_alloc_block
),
));
}
if (mdb.first_alloc_block as u32) < vbm_end {
} else if mdb.first_alloc_block < 3 {
errors.push(hfs_issue(
HfsFsckCode::MdbAlBlStTooLow,
format!(
"first allocation block start sector {} is too low (must be >= 3)",
mdb.first_alloc_block
),
));
}
} else if mdb.first_alloc_block < 3 {
errors.push(hfs_issue(
HfsFsckCode::MdbAlBlStTooLow,
format!(
"first allocation block start sector {} is too low (must be >= 3)",
mdb.first_alloc_block
),
));
}
if mdb.block_size > 0 && mdb.total_blocks > 0 {
let volume_size = mdb.total_blocks as u64 * mdb.block_size as u64;
let clump_checks: [(&str, usize); 3] = [
("default clump size (drClpSiz)", 24),
("extents file clump size (drXTClpSiz)", 74),
("catalog file clump size (drCTClpSiz)", 78),
];
for (label, offset) in &clump_checks {
let clump = BigEndian::read_u32(&mdb.raw_sector[*offset..*offset + 4]);
if clump == 0 {
continue; }
#[allow(clippy::manual_is_multiple_of)]
if clump % mdb.block_size != 0 {
errors.push(hfs_issue(
HfsFsckCode::InvalidClumpSize,
format!(
"{}: {} is not a multiple of block size {}",
label, clump, mdb.block_size
),
));
}
if clump as u64 > volume_size {
errors.push(hfs_issue(
HfsFsckCode::InvalidClumpSize,
format!("{}: {} exceeds volume size {}", label, clump, volume_size),
));
}
}
}
}