use std::io::{Read, Seek, SeekFrom};
use crate::diag;
use crate::partition::PartitionEntry;
use crate::Error;
const SECTOR_LEN: usize = 512;
const LOGICAL_ENTRY: usize = 446;
const NEXT_ENTRY: usize = 462;
const SLACK: usize = 478;
const BOOT_SIG: usize = 510;
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct EbrEntry {
pub ebr_offset: u64,
pub ebr_lba: u64,
pub logical: PartitionEntry,
pub logical_lba_start: u64,
pub slack: [u8; 32],
pub has_slack: bool,
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct EbrChain {
pub entries: Vec<EbrEntry>,
pub had_cycle: bool,
pub depth_exceeded: bool,
}
impl EbrChain {
#[must_use]
pub fn empty() -> Self {
Self::default()
}
}
const MAX_DEPTH: usize = 64;
pub fn walk_ebr_chain<R: Read + Seek>(
reader: &mut R,
ext_start_lba: u64,
sector_size: u64,
) -> Result<EbrChain, Error> {
let mut entries = Vec::new();
let mut had_cycle = false;
let mut depth_exceeded = false;
let mut visited = std::collections::HashSet::new();
let mut next_ebr_lba = ext_start_lba;
loop {
if entries.len() >= MAX_DEPTH {
depth_exceeded = true;
break;
}
if !visited.insert(next_ebr_lba) {
had_cycle = true;
break;
}
let Some(ebr_byte_offset) = next_ebr_lba.checked_mul(sector_size) else {
break; };
reader.seek(SeekFrom::Start(ebr_byte_offset))?;
let mut sector = [0u8; SECTOR_LEN];
if reader.read_exact(&mut sector).is_err() {
diag::ebr_truncated(next_ebr_lba);
break; }
if sector[BOOT_SIG] != 0x55 || sector[BOOT_SIG + 1] != 0xAA {
diag::ebr_no_signature(next_ebr_lba);
break;
}
let logical_raw: &[u8; 16] = sector[LOGICAL_ENTRY..NEXT_ENTRY].try_into().unwrap();
let next_raw: &[u8; 16] = sector[NEXT_ENTRY..SLACK].try_into().unwrap();
let slack_bytes: [u8; 32] = sector[SLACK..BOOT_SIG].try_into().unwrap();
let logical = PartitionEntry::from_bytes(logical_raw);
let next_entry = PartitionEntry::from_bytes(next_raw);
let logical_lba_start = next_ebr_lba.saturating_add(logical.lba_start as u64);
let has_slack = slack_bytes.iter().any(|&b| b != 0);
entries.push(EbrEntry {
ebr_offset: ebr_byte_offset,
ebr_lba: next_ebr_lba,
logical,
logical_lba_start,
slack: slack_bytes,
has_slack,
});
if next_entry.lba_start == 0 {
break;
}
let Some(next_lba) = ext_start_lba.checked_add(next_entry.lba_start as u64) else {
break; };
next_ebr_lba = next_lba;
}
Ok(EbrChain {
entries,
had_cycle,
depth_exceeded,
})
}