ewf_image/writer/acquisition/
source.rs1use std::io::{self, Read, Seek, SeekFrom, Write};
2use std::ops::ControlFlow;
3
4use super::super::{encode_chunks, segment_path};
5use super::{AcquisitionError, AcquisitionWriter, EwfError, Result};
6
7const AUTOMATIC_BULK_READ_BYTES: usize = 256 * 1024;
8
9#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
11pub enum UnreadableSectorPolicy {
12 #[default]
14 Stop,
15 ZeroFill,
19}
20
21#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct AcquisitionReadOptions {
25 pub retries: u32,
29 pub unreadable_sector_policy: UnreadableSectorPolicy,
31 pub maximum_error_ranges: usize,
35 pub checkpoint_interval: Option<u64>,
38}
39
40impl Default for AcquisitionReadOptions {
41 fn default() -> Self {
42 Self {
43 retries: 2,
44 unreadable_sector_policy: UnreadableSectorPolicy::Stop,
45 maximum_error_ranges: 65_536,
46 checkpoint_interval: None,
47 }
48 }
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53#[non_exhaustive]
54pub struct AcquisitionProgress {
55 pub source_size: u64,
57 pub bytes_written: u64,
59 pub checkpoint_bytes: u64,
61 pub sealed_segments: usize,
63 pub substituted_sectors: u64,
65 pub read_attempts: u64,
67 pub retry_attempts: u64,
69 pub read_offset: u64,
71 pub read_error: Option<io::ErrorKind>,
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77pub enum AcquisitionStatus {
78 Complete,
81 Cancelled,
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
87#[non_exhaustive]
88pub struct AcquisitionOutcome {
89 pub status: AcquisitionStatus,
91 pub progress: AcquisitionProgress,
93}
94
95impl AcquisitionWriter {
96 pub fn acquire_from<R: Read + Seek>(
99 &mut self,
100 source: &mut R,
101 options: &AcquisitionReadOptions,
102 ) -> Result<AcquisitionOutcome> {
103 self.acquire_with_progress(source, options, |_| ControlFlow::Continue(()))
104 }
105
106 pub fn acquire_with_progress<R: Read + Seek>(
119 &mut self,
120 source: &mut R,
121 options: &AcquisitionReadOptions,
122 mut on_progress: impl FnMut(AcquisitionProgress) -> ControlFlow<()>,
123 ) -> Result<AcquisitionOutcome> {
124 self.ensure_healthy()?;
125 let sector_size = self.options.bytes_per_sector as usize;
126 if !self.offset.is_multiple_of(sector_size as u64)
127 || options.retries > 100
128 || options.maximum_error_ranges == 0
129 || self.errors.len() > options.maximum_error_ranges
130 || options.checkpoint_interval.is_some_and(|interval| {
131 interval == 0 || !interval.is_multiple_of(self.chunk_size as u64)
132 })
133 {
134 return Err(EwfError::Malformed(
135 "invalid acquisition read options or unaligned source position".into(),
136 ));
137 }
138 let maximum_segment_bytes = self.chunk_size as u64 * self.chunks_per_segment as u64;
142 let interval = options
143 .checkpoint_interval
144 .unwrap_or(maximum_segment_bytes)
145 .min(maximum_segment_bytes);
146 let remaining_segments = (self.source_size - self.checkpoint_offset()).div_ceil(interval);
147 let final_segment =
148 u64::try_from(self.sealed.len()).expect("segment count fits u64") + remaining_segments;
149 segment_path(
150 &self.first,
151 usize::try_from(final_segment)
152 .map_err(|_| EwfError::Unsupported("too many acquisition segments".into()))?,
153 )?;
154 let mut progress = self.source_progress();
155 let result = self.acquire_loop(source, options, &mut progress, &mut on_progress);
156 let status = match result {
157 Ok(()) => AcquisitionStatus::Complete,
158 Err(EwfError::Aborted) => AcquisitionStatus::Cancelled,
159 Err(error) => {
160 if !self.failed {
161 self.checkpoint()?;
162 }
163 return Err(error);
164 }
165 };
166 self.checkpoint()?;
167 self.refresh_source_progress(&mut progress);
168 Ok(AcquisitionOutcome { status, progress })
169 }
170
171 fn source_progress(&self) -> AcquisitionProgress {
172 let mut progress = AcquisitionProgress {
173 source_size: self.source_size,
174 bytes_written: 0,
175 checkpoint_bytes: 0,
176 sealed_segments: 0,
177 substituted_sectors: 0,
178 read_attempts: 0,
179 retry_attempts: 0,
180 read_offset: self.offset,
181 read_error: None,
182 };
183 self.refresh_source_progress(&mut progress);
184 progress
185 }
186
187 fn refresh_source_progress(&self, progress: &mut AcquisitionProgress) {
188 progress.bytes_written = self.offset;
189 progress.checkpoint_bytes = self.checkpoint_offset();
190 progress.sealed_segments = self.sealed.len();
191 progress.substituted_sectors = self.substituted_sectors;
192 }
193
194 fn acquire_loop<R: Read + Seek>(
195 &mut self,
196 source: &mut R,
197 options: &AcquisitionReadOptions,
198 progress: &mut AcquisitionProgress,
199 callback: &mut impl FnMut(AcquisitionProgress) -> ControlFlow<()>,
200 ) -> Result<()> {
201 notify(*progress, callback)?;
202 let mut buffer = vec![0; automatic_bulk_read_bytes(self.chunk_size)];
203 let sector_size = self.options.bytes_per_sector as usize;
204 while self.offset < self.source_size {
205 let size = (if self.pending.is_empty() {
206 buffer.len()
207 } else {
208 self.chunk_size - self.pending.len()
209 })
210 .min(buffer.len())
211 .min((self.source_size - self.offset).min(usize::MAX as u64) as usize);
212 let read = if size > sector_size {
213 match read_attempt(source, &mut buffer[..size], self.offset, progress, callback) {
214 Ok(()) => true,
215 Err(AttemptError::Read(error)) if recoverable(&error) => false,
216 Err(error) => return Err(error.into_ewf()),
217 }
218 } else {
219 false
220 };
221 if read {
222 self.append_healthy_batch(&buffer[..size], options, progress, callback)?;
223 continue;
224 }
225 for sector in buffer[..size].chunks_mut(sector_size) {
228 let mut last_error = None;
229 for attempt in 0..=options.retries {
230 if attempt > 0 {
231 progress.retry_attempts += 1;
232 }
233 match read_attempt(source, sector, self.offset, progress, callback) {
234 Ok(()) => {
235 last_error = None;
236 break;
237 }
238 Err(AttemptError::Read(error)) if recoverable(&error) => {
239 last_error = Some(error);
240 }
241 Err(error) => return Err(error.into_ewf()),
242 }
243 }
244 if let Some(error) = last_error {
245 if options.unreadable_sector_policy == UnreadableSectorPolicy::Stop {
246 return Err(error.into());
247 }
248 self.record_substitution(options.maximum_error_ranges)?;
249 sector.fill(0);
250 }
251 self.write_all(sector)?;
252 self.after_source_write(options, progress, callback)?;
253 }
254 }
255 Ok(())
256 }
257
258 fn append_healthy_batch(
259 &mut self,
260 bytes: &[u8],
261 options: &AcquisitionReadOptions,
262 progress: &mut AcquisitionProgress,
263 callback: &mut impl FnMut(AcquisitionProgress) -> ControlFlow<()>,
264 ) -> Result<()> {
265 self.ensure_healthy()?;
266 if !self.pending.is_empty()
267 || bytes.is_empty()
268 || bytes.len() as u64 > self.source_size - self.offset
269 {
270 return Err(EwfError::Malformed(
271 "invalid healthy acquisition batch".into(),
272 ));
273 }
274
275 let processing_started = std::time::Instant::now();
276 let mut hash_states = Vec::with_capacity(bytes.len().div_ceil(self.chunk_size));
277 let mut hashes = self.hashes.clone();
278 let mut lengths = Vec::with_capacity(hash_states.capacity());
279 let chunks = bytes
280 .chunks(self.chunk_size)
281 .map(|chunk| {
282 hashes.update(chunk);
283 hash_states.push(hashes.clone());
284 lengths.push(chunk.len());
285 chunk.to_vec()
286 })
287 .collect();
288 let encoded = match encode_chunks(
289 chunks,
290 self.options.compression,
291 self.options.compression_values,
292 self.chunk_size as u64,
293 false,
294 false,
295 ) {
296 Ok(encoded) => encoded,
297 Err(error) => {
298 self.failed = true;
299 return Err(error);
300 }
301 };
302 self.processing_duration += processing_started.elapsed();
303
304 for ((chunk, length), hashes) in encoded.into_iter().zip(lengths).zip(hash_states) {
305 self.offset += length as u64;
306 self.hashes = hashes;
307 let scratch_started = std::time::Instant::now();
308 let descriptor = match self.spool.as_mut().expect("active spool").append(chunk) {
309 Ok(descriptor) => descriptor,
310 Err(error) => {
311 self.failed = true;
312 return Err(error);
313 }
314 };
315 self.chunks.push(descriptor);
316 self.scratch_write_duration += scratch_started.elapsed();
317 if (self.chunks.len() == self.chunks_per_segment || self.offset == self.source_size)
318 && let Err(error) = self.seal(self.offset == self.source_size)
319 {
320 self.failed = true;
321 return Err(error);
322 }
323 self.after_source_write(options, progress, callback)?;
324 }
325 Ok(())
326 }
327
328 fn after_source_write(
329 &mut self,
330 options: &AcquisitionReadOptions,
331 progress: &mut AcquisitionProgress,
332 callback: &mut impl FnMut(AcquisitionProgress) -> ControlFlow<()>,
333 ) -> Result<()> {
334 if options
335 .checkpoint_interval
336 .is_some_and(|interval| self.offset - self.checkpoint_offset() >= interval)
337 {
338 self.checkpoint()?;
339 }
340 self.refresh_source_progress(progress);
341 notify(*progress, callback)
342 }
343
344 fn record_substitution(&mut self, maximum_ranges: usize) -> Result<()> {
345 let first_sector = self.offset / u64::from(self.options.bytes_per_sector);
346 if first_sector > u64::from(u32::MAX) {
349 return Err(EwfError::Unsupported(
350 "unreadable sector exceeds EWF1 error-table addressing".into(),
351 ));
352 }
353 if let Some(last) = self.errors.last_mut()
354 && last.first_sector + last.sector_count == first_sector
355 && last.sector_count < u64::from(u32::MAX)
356 {
357 last.sector_count += 1;
358 } else {
359 if self.errors.len() >= maximum_ranges {
360 return Err(EwfError::Unsupported(
361 "acquisition error-range limit reached".into(),
362 ));
363 }
364 self.errors.push(AcquisitionError {
365 first_sector,
366 sector_count: 1,
367 });
368 }
369 self.substituted_sectors += 1;
370 Ok(())
371 }
372}
373
374fn automatic_bulk_read_bytes(chunk_size: usize) -> usize {
375 (AUTOMATIC_BULK_READ_BYTES / chunk_size).max(1) * chunk_size
376}
377
378enum AttemptError {
379 Read(io::Error),
380 Fatal(EwfError),
381}
382
383impl AttemptError {
384 fn into_ewf(self) -> EwfError {
385 match self {
386 Self::Read(error) => error.into(),
387 Self::Fatal(error) => error,
388 }
389 }
390}
391
392fn read_attempt<R: Read + Seek>(
393 source: &mut R,
394 buffer: &mut [u8],
395 offset: u64,
396 progress: &mut AcquisitionProgress,
397 callback: &mut impl FnMut(AcquisitionProgress) -> ControlFlow<()>,
398) -> std::result::Result<(), AttemptError> {
399 progress.read_offset = offset;
400 progress.read_error = None;
401 notify(*progress, callback).map_err(AttemptError::Fatal)?;
402 let positioned = source
403 .seek(SeekFrom::Start(offset))
404 .map_err(|error| AttemptError::Fatal(error.into()))?;
405 if positioned != offset {
406 return Err(AttemptError::Fatal(EwfError::Malformed(
407 "source seek returned a different offset".into(),
408 )));
409 }
410 progress.read_attempts += 1;
411 let mut filled = 0;
412 while filled < buffer.len() {
413 let result = source.read(&mut buffer[filled..]);
414 let error = match result {
415 Ok(0) => Some(io::Error::new(
416 io::ErrorKind::UnexpectedEof,
417 "source ended before declared acquisition size",
418 )),
419 Ok(size) if size <= buffer.len() - filled => {
420 filled += size;
421 None
422 }
423 Ok(_) => Some(io::Error::new(
424 io::ErrorKind::InvalidInput,
425 "source returned an invalid read length",
426 )),
427 Err(error) => Some(error),
428 };
429 if let Some(error) = error {
430 progress.read_error = Some(error.kind());
431 notify(*progress, callback).map_err(AttemptError::Fatal)?;
432 return Err(AttemptError::Read(error));
433 }
434 notify(*progress, callback).map_err(AttemptError::Fatal)?;
435 }
436 Ok(())
437}
438
439fn recoverable(error: &io::Error) -> bool {
440 !matches!(
441 error.kind(),
442 io::ErrorKind::UnexpectedEof
443 | io::ErrorKind::PermissionDenied
444 | io::ErrorKind::InvalidInput
445 | io::ErrorKind::Unsupported
446 | io::ErrorKind::NotFound
447 | io::ErrorKind::Interrupted
448 | io::ErrorKind::TimedOut
449 | io::ErrorKind::WouldBlock
450 | io::ErrorKind::NotConnected
451 | io::ErrorKind::ConnectionAborted
452 | io::ErrorKind::ConnectionReset
453 | io::ErrorKind::BrokenPipe
454 )
455}
456
457fn notify(
458 progress: AcquisitionProgress,
459 callback: &mut impl FnMut(AcquisitionProgress) -> ControlFlow<()>,
460) -> Result<()> {
461 if callback(progress).is_break() {
462 Err(EwfError::Aborted)
463 } else {
464 Ok(())
465 }
466}