use std::io::{self, Read, Seek, SeekFrom, Write};
use std::ops::ControlFlow;
use super::super::segment_path;
use super::{AcquisitionError, AcquisitionWriter, EwfError, Result};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum UnreadableSectorPolicy {
#[default]
Stop,
ZeroFill,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AcquisitionReadOptions {
pub retries: u32,
pub unreadable_sector_policy: UnreadableSectorPolicy,
pub maximum_error_ranges: usize,
pub checkpoint_interval: Option<u64>,
}
impl Default for AcquisitionReadOptions {
fn default() -> Self {
Self {
retries: 2,
unreadable_sector_policy: UnreadableSectorPolicy::Stop,
maximum_error_ranges: 65_536,
checkpoint_interval: None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct AcquisitionProgress {
pub source_size: u64,
pub bytes_written: u64,
pub checkpoint_bytes: u64,
pub sealed_segments: usize,
pub substituted_sectors: u64,
pub read_attempts: u64,
pub retry_attempts: u64,
pub read_offset: u64,
pub read_error: Option<io::ErrorKind>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AcquisitionStatus {
Complete,
Cancelled,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct AcquisitionOutcome {
pub status: AcquisitionStatus,
pub progress: AcquisitionProgress,
}
impl AcquisitionWriter {
pub fn acquire_from<R: Read + Seek>(
&mut self,
source: &mut R,
options: &AcquisitionReadOptions,
) -> Result<AcquisitionOutcome> {
self.acquire_with_progress(source, options, |_| ControlFlow::Continue(()))
}
pub fn acquire_with_progress<R: Read + Seek>(
&mut self,
source: &mut R,
options: &AcquisitionReadOptions,
mut on_progress: impl FnMut(AcquisitionProgress) -> ControlFlow<()>,
) -> Result<AcquisitionOutcome> {
self.ensure_healthy()?;
let sector_size = self.options.bytes_per_sector as usize;
if !self.offset.is_multiple_of(sector_size as u64)
|| options.retries > 100
|| options.maximum_error_ranges == 0
|| self.errors.len() > options.maximum_error_ranges
|| options.checkpoint_interval.is_some_and(|interval| {
interval == 0 || !interval.is_multiple_of(self.chunk_size as u64)
})
{
return Err(EwfError::Malformed(
"invalid acquisition read options or unaligned source position".into(),
));
}
let maximum_segment_bytes = self.chunk_size as u64 * self.chunks_per_segment as u64;
let interval = options
.checkpoint_interval
.unwrap_or(maximum_segment_bytes)
.min(maximum_segment_bytes);
let remaining_segments = (self.source_size - self.checkpoint_offset()).div_ceil(interval);
let final_segment =
u64::try_from(self.sealed.len()).expect("segment count fits u64") + remaining_segments;
segment_path(
&self.first,
usize::try_from(final_segment)
.map_err(|_| EwfError::Unsupported("too many acquisition segments".into()))?,
)?;
let mut progress = self.source_progress();
let result = self.acquire_loop(source, options, &mut progress, &mut on_progress);
let status = match result {
Ok(()) => AcquisitionStatus::Complete,
Err(EwfError::Aborted) => AcquisitionStatus::Cancelled,
Err(error) => {
if !self.failed {
self.checkpoint()?;
}
return Err(error);
}
};
self.checkpoint()?;
self.refresh_source_progress(&mut progress);
Ok(AcquisitionOutcome { status, progress })
}
fn source_progress(&self) -> AcquisitionProgress {
let mut progress = AcquisitionProgress {
source_size: self.source_size,
bytes_written: 0,
checkpoint_bytes: 0,
sealed_segments: 0,
substituted_sectors: 0,
read_attempts: 0,
retry_attempts: 0,
read_offset: self.offset,
read_error: None,
};
self.refresh_source_progress(&mut progress);
progress
}
fn refresh_source_progress(&self, progress: &mut AcquisitionProgress) {
progress.bytes_written = self.offset;
progress.checkpoint_bytes = self.checkpoint_offset();
progress.sealed_segments = self.sealed.len();
progress.substituted_sectors = self.substituted_sectors;
}
fn acquire_loop<R: Read + Seek>(
&mut self,
source: &mut R,
options: &AcquisitionReadOptions,
progress: &mut AcquisitionProgress,
callback: &mut impl FnMut(AcquisitionProgress) -> ControlFlow<()>,
) -> Result<()> {
notify(*progress, callback)?;
let mut buffer = vec![0; self.chunk_size];
let sector_size = self.options.bytes_per_sector as usize;
while self.offset < self.source_size {
let size = (self.chunk_size - self.pending.len())
.min((self.source_size - self.offset).min(usize::MAX as u64) as usize);
let read = if size > sector_size {
match read_attempt(source, &mut buffer[..size], self.offset, progress, callback) {
Ok(()) => true,
Err(AttemptError::Read(error)) if recoverable(&error) => false,
Err(error) => return Err(error.into_ewf()),
}
} else {
false
};
if read {
self.write_all(&buffer[..size])?;
self.after_source_write(options, progress, callback)?;
continue;
}
for sector in buffer[..size].chunks_mut(sector_size) {
let mut last_error = None;
for attempt in 0..=options.retries {
if attempt > 0 {
progress.retry_attempts += 1;
}
match read_attempt(source, sector, self.offset, progress, callback) {
Ok(()) => {
last_error = None;
break;
}
Err(AttemptError::Read(error)) if recoverable(&error) => {
last_error = Some(error);
}
Err(error) => return Err(error.into_ewf()),
}
}
if let Some(error) = last_error {
if options.unreadable_sector_policy == UnreadableSectorPolicy::Stop {
return Err(error.into());
}
self.record_substitution(options.maximum_error_ranges)?;
sector.fill(0);
}
self.write_all(sector)?;
self.after_source_write(options, progress, callback)?;
}
}
Ok(())
}
fn after_source_write(
&mut self,
options: &AcquisitionReadOptions,
progress: &mut AcquisitionProgress,
callback: &mut impl FnMut(AcquisitionProgress) -> ControlFlow<()>,
) -> Result<()> {
if options
.checkpoint_interval
.is_some_and(|interval| self.offset - self.checkpoint_offset() >= interval)
{
self.checkpoint()?;
}
self.refresh_source_progress(progress);
notify(*progress, callback)
}
fn record_substitution(&mut self, maximum_ranges: usize) -> Result<()> {
let first_sector = self.offset / u64::from(self.options.bytes_per_sector);
if first_sector > u64::from(u32::MAX) {
return Err(EwfError::Unsupported(
"unreadable sector exceeds EWF1 error-table addressing".into(),
));
}
if let Some(last) = self.errors.last_mut()
&& last.first_sector + last.sector_count == first_sector
&& last.sector_count < u64::from(u32::MAX)
{
last.sector_count += 1;
} else {
if self.errors.len() >= maximum_ranges {
return Err(EwfError::Unsupported(
"acquisition error-range limit reached".into(),
));
}
self.errors.push(AcquisitionError {
first_sector,
sector_count: 1,
});
}
self.substituted_sectors += 1;
Ok(())
}
}
enum AttemptError {
Read(io::Error),
Fatal(EwfError),
}
impl AttemptError {
fn into_ewf(self) -> EwfError {
match self {
Self::Read(error) => error.into(),
Self::Fatal(error) => error,
}
}
}
fn read_attempt<R: Read + Seek>(
source: &mut R,
buffer: &mut [u8],
offset: u64,
progress: &mut AcquisitionProgress,
callback: &mut impl FnMut(AcquisitionProgress) -> ControlFlow<()>,
) -> std::result::Result<(), AttemptError> {
progress.read_offset = offset;
progress.read_error = None;
notify(*progress, callback).map_err(AttemptError::Fatal)?;
let positioned = source
.seek(SeekFrom::Start(offset))
.map_err(|error| AttemptError::Fatal(error.into()))?;
if positioned != offset {
return Err(AttemptError::Fatal(EwfError::Malformed(
"source seek returned a different offset".into(),
)));
}
progress.read_attempts += 1;
let mut filled = 0;
while filled < buffer.len() {
let result = source.read(&mut buffer[filled..]);
let error = match result {
Ok(0) => Some(io::Error::new(
io::ErrorKind::UnexpectedEof,
"source ended before declared acquisition size",
)),
Ok(size) if size <= buffer.len() - filled => {
filled += size;
None
}
Ok(_) => Some(io::Error::new(
io::ErrorKind::InvalidInput,
"source returned an invalid read length",
)),
Err(error) => Some(error),
};
if let Some(error) = error {
progress.read_error = Some(error.kind());
notify(*progress, callback).map_err(AttemptError::Fatal)?;
return Err(AttemptError::Read(error));
}
notify(*progress, callback).map_err(AttemptError::Fatal)?;
}
Ok(())
}
fn recoverable(error: &io::Error) -> bool {
!matches!(
error.kind(),
io::ErrorKind::UnexpectedEof
| io::ErrorKind::PermissionDenied
| io::ErrorKind::InvalidInput
| io::ErrorKind::Unsupported
| io::ErrorKind::NotFound
| io::ErrorKind::Interrupted
| io::ErrorKind::TimedOut
| io::ErrorKind::WouldBlock
| io::ErrorKind::NotConnected
| io::ErrorKind::ConnectionAborted
| io::ErrorKind::ConnectionReset
| io::ErrorKind::BrokenPipe
)
}
fn notify(
progress: AcquisitionProgress,
callback: &mut impl FnMut(AcquisitionProgress) -> ControlFlow<()>,
) -> Result<()> {
if callback(progress).is_break() {
Err(EwfError::Aborted)
} else {
Ok(())
}
}