use crate::error::DiscError;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct HardDiskGeo {
pub cylinders: u32,
pub heads: u32,
pub sectors: u32,
pub bytes_per_sector: u32,
}
impl HardDiskGeo {
pub fn new(cylinders: u32, heads: u32, sectors: u32, bytes_per_sector: u32) -> Self {
Self {
cylinders,
heads,
sectors,
bytes_per_sector,
}
}
pub fn total_sectors(&self) -> u64 {
self.cylinders as u64 * self.heads as u64 * self.sectors as u64
}
pub fn total_bytes(&self) -> u64 {
self.total_sectors() * self.bytes_per_sector as u64
}
pub fn to_metadata_string(&self) -> String {
format!(
"CYLS:{},HEADS:{},SECS:{},BPS:{}",
self.cylinders, self.heads, self.sectors, self.bytes_per_sector
)
}
pub fn parse_metadata(s: &str) -> Result<Self, DiscError> {
let mut cyls = None;
let mut heads = None;
let mut secs = None;
let mut bps = None;
let trimmed = s.trim().trim_matches('\0');
for part in trimmed.split([',', ' ']) {
let part = part.trim();
if part.is_empty() {
continue;
}
if let Some((k, v)) = part.split_once(':') {
match k.trim() {
"CYLS" => {
cyls = Some(v.trim().parse::<u32>().map_err(|_| {
DiscError::InvalidMetadata(format!("invalid CYLS in '{}'", s))
})?);
}
"HEADS" => {
heads = Some(v.trim().parse::<u32>().map_err(|_| {
DiscError::InvalidMetadata(format!("invalid HEADS in '{}'", s))
})?);
}
"SECS" => {
secs = Some(v.trim().parse::<u32>().map_err(|_| {
DiscError::InvalidMetadata(format!("invalid SECS in '{}'", s))
})?);
}
"BPS" => {
bps = Some(v.trim().parse::<u32>().map_err(|_| {
DiscError::InvalidMetadata(format!("invalid BPS in '{}'", s))
})?);
}
_ => {}
}
}
}
match (cyls, heads, secs, bps) {
(Some(c), Some(h), Some(s), Some(b)) => Ok(Self::new(c, h, s, b)),
_ => Err(DiscError::InvalidMetadata(format!(
"incomplete hard disk geometry metadata: '{}'",
s
))),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct HdTemplate {
pub manufacturer: &'static str,
pub model: &'static str,
pub cylinders: u32,
pub heads: u32,
pub sectors: u32,
pub sector_size: u32,
}
pub const HD_TEMPLATES: &[HdTemplate] = &[
HdTemplate {
manufacturer: "Conner",
model: "CFA170A",
cylinders: 332,
heads: 16,
sectors: 63,
sector_size: 512,
},
HdTemplate {
manufacturer: "Rodime",
model: "R0201",
cylinders: 321,
heads: 2,
sectors: 16,
sector_size: 512,
},
HdTemplate {
manufacturer: "Rodime",
model: "R0202",
cylinders: 321,
heads: 4,
sectors: 16,
sector_size: 512,
},
HdTemplate {
manufacturer: "Rodime",
model: "R0203",
cylinders: 321,
heads: 6,
sectors: 16,
sector_size: 512,
},
HdTemplate {
manufacturer: "Rodime",
model: "R0204",
cylinders: 321,
heads: 8,
sectors: 16,
sector_size: 512,
},
HdTemplate {
manufacturer: "Seagate",
model: "ST-213",
cylinders: 615,
heads: 2,
sectors: 17,
sector_size: 512,
},
HdTemplate {
manufacturer: "Seagate",
model: "ST-225",
cylinders: 615,
heads: 4,
sectors: 17,
sector_size: 512,
},
HdTemplate {
manufacturer: "Seagate",
model: "ST-251",
cylinders: 820,
heads: 6,
sectors: 17,
sector_size: 512,
},
HdTemplate {
manufacturer: "Seagate",
model: "ST-3600N",
cylinders: 1877,
heads: 7,
sectors: 76,
sector_size: 512,
},
HdTemplate {
manufacturer: "Maxtor",
model: "LXT-213S",
cylinders: 1314,
heads: 7,
sectors: 53,
sector_size: 512,
},
HdTemplate {
manufacturer: "Maxtor",
model: "LXT-340S",
cylinders: 1574,
heads: 7,
sectors: 70,
sector_size: 512,
},
HdTemplate {
manufacturer: "Maxtor",
model: "MXT-540SL",
cylinders: 2466,
heads: 7,
sectors: 87,
sector_size: 512,
},
HdTemplate {
manufacturer: "Micropolis",
model: "1528",
cylinders: 2094,
heads: 15,
sectors: 83,
sector_size: 512,
},
HdTemplate {
manufacturer: "Quantum",
model: "Fireball CR 4.3 AT",
cylinders: 14848,
heads: 9,
sectors: 63,
sector_size: 512,
},
HdTemplate {
manufacturer: "Quantum",
model: "Fireball CR 6.4 AT",
cylinders: 13328,
heads: 15,
sectors: 63,
sector_size: 512,
},
HdTemplate {
manufacturer: "Quantum",
model: "Fireball CR 8.4 AT",
cylinders: 16383,
heads: 16,
sectors: 63,
sector_size: 512,
},
HdTemplate {
manufacturer: "Quantum",
model: "Fireball CR 13.0 AT",
cylinders: 25228,
heads: 16,
sectors: 63,
sector_size: 512,
},
];
pub fn guess_chs(file_size: u64, sector_size: u32) -> Result<HardDiskGeo, DiscError> {
if file_size == 0 || sector_size == 0 {
return Err(DiscError::InvalidMetadata(
"cannot guess CHS for empty file".into(),
));
}
let mut total_sectors = (file_size / sector_size as u64) as u32;
loop {
for cur_sectors in (2..=63).rev() {
if total_sectors.is_multiple_of(cur_sectors) {
let total_heads = total_sectors / cur_sectors;
for cur_heads in (2..=16).rev() {
if total_heads.is_multiple_of(cur_heads) {
let cylinders = total_heads / cur_heads;
return Ok(HardDiskGeo::new(
cylinders,
cur_heads,
cur_sectors,
sector_size,
));
}
}
}
}
total_sectors = total_sectors.checked_add(1).ok_or_else(|| {
DiscError::InvalidMetadata("overflow searching for CHS geometry".into())
})?;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hard_disk_geo_roundtrip() {
let geo = HardDiskGeo::new(1024, 16, 63, 512);
let s = geo.to_metadata_string();
assert_eq!(s, "CYLS:1024,HEADS:16,SECS:63,BPS:512");
let parsed = HardDiskGeo::parse_metadata(&s).expect("parse metadata");
assert_eq!(geo, parsed);
}
#[test]
fn test_guess_chs_64mb() {
let geo = guess_chs(67_108_864, 512).expect("guess chs 64mb");
assert_eq!(geo.cylinders, 256);
assert_eq!(geo.heads, 16);
assert_eq!(geo.sectors, 32);
assert_eq!(geo.bytes_per_sector, 512);
}
}