use std::fs::File;
use std::io::{Read, Write};
use std::path::Path;
use crate::enhancements::metadata::tags::{HARD_DISK_IDENT_METADATA_TAG, HARD_DISK_METADATA_TAG};
use crate::streaming::{run_compression, StreamingSource};
use crate::{sys, Chd, ChdCompressor, ChdError, CompressionProgress, Result, CHD_CODEC_ZLIB};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct HdGeometry {
pub cylinders: u32,
pub heads: u32,
pub sectors: u32,
pub sector_bytes: u32,
}
impl HdGeometry {
pub fn logical_bytes(&self) -> u64 {
u64::from(self.cylinders)
* u64::from(self.heads)
* u64::from(self.sectors)
* u64::from(self.sector_bytes)
}
}
#[derive(Debug, Clone)]
pub struct HdCreateOptions {
pub logical_size: u64,
pub hunk_size: u32,
pub unit_size: u32,
pub codecs: [u32; 4],
pub geometry: Option<HdGeometry>,
pub ident: Option<Vec<u8>>,
}
impl Default for HdCreateOptions {
fn default() -> Self {
Self {
logical_size: 0,
hunk_size: 4096,
unit_size: 512,
codecs: [CHD_CODEC_ZLIB, 0, 0, 0],
geometry: None,
ident: None,
}
}
}
pub fn compute_chs(logical_bytes: u64, sector_size: u32) -> Result<HdGeometry> {
if logical_bytes == 0 || sector_size == 0 {
return Err(ChdError::InvalidData);
}
if !logical_bytes.is_multiple_of(u64::from(sector_size)) {
return Err(ChdError::InvalidData);
}
let initial = logical_bytes / u64::from(sector_size);
let mut total: u64 = initial;
loop {
for cur_sectors in (2u32..=63).rev() {
if total.is_multiple_of(u64::from(cur_sectors)) {
let total_heads = total / u64::from(cur_sectors);
for cur_heads in (2u32..=16).rev() {
if total_heads.is_multiple_of(u64::from(cur_heads)) {
let cylinders = (total_heads / u64::from(cur_heads)) as u32;
return Ok(HdGeometry {
cylinders,
heads: cur_heads,
sectors: cur_sectors,
sector_bytes: sector_size,
});
}
}
}
}
total = total.checked_add(1).ok_or(ChdError::InvalidData)?;
}
}
pub fn format_gddd(g: HdGeometry) -> Vec<u8> {
let mut s = format!(
"CYLS:{},HEADS:{},SECS:{},BPS:{}",
g.cylinders, g.heads, g.sectors, g.sector_bytes
)
.into_bytes();
s.push(0);
s
}
pub fn read_geometry(chd: &Chd) -> Result<HdGeometry> {
let raw = chd.read_metadata(HARD_DISK_METADATA_TAG, 0)?;
let s = std::str::from_utf8(&raw)
.map_err(|_| ChdError::InvalidMetadata)?
.trim_end_matches('\0');
let parts: Vec<_> = s.split(',').collect();
if parts.len() != 4 {
return Err(ChdError::InvalidMetadata);
}
fn parse_kv(s: &str, prefix: &str) -> Result<u32> {
s.strip_prefix(prefix)
.ok_or(ChdError::InvalidMetadata)?
.parse::<u32>()
.map_err(|_| ChdError::InvalidMetadata)
}
Ok(HdGeometry {
cylinders: parse_kv(parts[0], "CYLS:")?,
heads: parse_kv(parts[1], "HEADS:")?,
sectors: parse_kv(parts[2], "SECS:")?,
sector_bytes: parse_kv(parts[3], "BPS:")?,
})
}
fn validate_options(o: &HdCreateOptions) -> Result<()> {
if o.unit_size == 0 || o.hunk_size == 0 {
return Err(ChdError::InvalidData);
}
if !o.hunk_size.is_multiple_of(o.unit_size) {
return Err(ChdError::InvalidData);
}
if !o.logical_size.is_multiple_of(u64::from(o.unit_size)) {
return Err(ChdError::InvalidData);
}
Ok(())
}
pub fn create_from_reader<R: Read>(
reader: R,
out_path: &Path,
opts: HdCreateOptions,
progress: &mut dyn FnMut(CompressionProgress),
cancel: &dyn Fn() -> bool,
) -> Result<()> {
validate_options(&opts)?;
let geom = match opts.geometry {
Some(g) => g,
None => compute_chs(opts.logical_size, opts.unit_size)?,
};
let source = StreamingSource::new(reader, opts.logical_size);
let mut compressor = ChdCompressor::new(source);
let path_str = out_path.to_str().ok_or(ChdError::InvalidFile)?.to_string();
compressor.create_file(
&path_str,
opts.logical_size,
opts.hunk_size,
opts.unit_size,
opts.codecs,
)?;
write_compressor_metadata(
&mut compressor,
HARD_DISK_METADATA_TAG,
0,
&format_gddd(geom),
1,
)?;
if let Some(ident) = &opts.ident {
write_compressor_metadata(&mut compressor, HARD_DISK_IDENT_METADATA_TAG, 0, ident, 1)?;
}
run_compression(compressor, out_path, progress, cancel)
}
pub fn create_from_path(
in_path: &Path,
out_path: &Path,
opts: HdCreateOptions,
progress: &mut dyn FnMut(CompressionProgress),
cancel: &dyn Fn() -> bool,
) -> Result<()> {
let mut effective = opts;
if effective.logical_size == 0 {
let meta = std::fs::metadata(in_path).map_err(|_| ChdError::InvalidFile)?;
effective.logical_size = meta.len();
}
let f = File::open(in_path).map_err(|_| ChdError::InvalidFile)?;
create_from_reader(f, out_path, effective, progress, cancel)
}
pub fn extract_to_writer<W: Write>(
chd_path: &Path,
mut writer: W,
progress: &mut dyn FnMut(u64),
) -> Result<()> {
let chd = Chd::open(chd_path.to_str().ok_or(ChdError::InvalidFile)?, false, None)?;
let total = chd.logical_bytes();
let hunk = chd.hunk_bytes() as usize;
let mut buf = vec![0u8; hunk];
let mut written: u64 = 0;
let mut offset: u64 = 0;
while offset < total {
let chunk = std::cmp::min(hunk as u64, total - offset) as usize;
chd.read_bytes(offset, &mut buf[..chunk])?;
writer
.write_all(&buf[..chunk])
.map_err(|_| ChdError::CompressionError)?;
offset += chunk as u64;
written += chunk as u64;
progress(written);
}
Ok(())
}
pub fn extract_to_path(
chd_path: &Path,
out_path: &Path,
progress: &mut dyn FnMut(u64),
) -> Result<()> {
let f = File::create(out_path).map_err(|_| ChdError::InvalidFile)?;
extract_to_writer(chd_path, f, progress)
}
fn write_compressor_metadata(
compressor: &mut ChdCompressor,
tag: u32,
index: u32,
data: &[u8],
flags: u8,
) -> Result<()> {
let raw = compressor.as_chd_file_ptr();
let err = unsafe {
sys::chd_shim_write_metadata(
raw,
tag,
index,
data.as_ptr() as *const _,
data.len() as u32,
flags,
)
};
if err == ChdError::NoError {
Ok(())
} else {
Err(err)
}
}
pub struct HdImage {
chd: Chd,
#[allow(dead_code)]
parent: Option<Chd>,
geometry: HdGeometry,
}
impl HdImage {
pub fn open(path: &Path) -> Result<Self> {
let chd = Chd::open(path.to_str().ok_or(ChdError::InvalidFile)?, true, None)?;
let geometry = read_geometry(&chd)?;
Ok(Self {
chd,
parent: None,
geometry,
})
}
pub fn open_with_diff(parent_path: &Path, diff_path: &Path) -> Result<Self> {
let parent = Chd::open(
parent_path.to_str().ok_or(ChdError::InvalidFile)?,
false,
None,
)?;
let logical = parent.logical_bytes();
let hunk_bytes = parent.hunk_bytes();
{
let mut diff = Chd::create_with_parent(
diff_path.to_str().ok_or(ChdError::InvalidFile)?,
logical,
hunk_bytes,
[0; 4],
&parent,
)?;
diff.clone_all_metadata(&parent)?;
}
drop(parent);
Self::reopen_diff(parent_path, diff_path)
}
pub fn reopen_diff(parent_path: &Path, diff_path: &Path) -> Result<Self> {
let parent = Chd::open(
parent_path.to_str().ok_or(ChdError::InvalidFile)?,
false,
None,
)?;
let chd = Chd::open(
diff_path.to_str().ok_or(ChdError::InvalidFile)?,
true,
Some(&parent),
)?;
let geometry = read_geometry(&chd)?;
Ok(Self {
chd,
parent: Some(parent),
geometry,
})
}
pub fn geometry(&self) -> HdGeometry {
self.geometry
}
pub fn sector_size(&self) -> u32 {
self.geometry.sector_bytes
}
pub fn sector_count(&self) -> u64 {
self.chd.logical_bytes() / u64::from(self.geometry.sector_bytes)
}
pub fn read_sector(&self, lba: u64, buf: &mut [u8]) -> Result<()> {
let ss = self.geometry.sector_bytes as usize;
if buf.len() != ss {
return Err(ChdError::InvalidData);
}
if lba >= self.sector_count() {
return Err(ChdError::InvalidData);
}
self.chd.read_bytes(lba * ss as u64, buf)
}
pub fn write_sector(&mut self, lba: u64, buf: &[u8]) -> Result<()> {
let ss = self.geometry.sector_bytes as usize;
if buf.len() != ss {
return Err(ChdError::InvalidData);
}
if lba >= self.sector_count() {
return Err(ChdError::InvalidData);
}
self.chd.write_bytes(lba * ss as u64, buf)
}
pub fn as_chd(&self) -> &Chd {
&self.chd
}
pub fn as_chd_mut(&mut self) -> &mut Chd {
&mut self.chd
}
}