ewf_image/writer/acquisition/
source.rs1use std::io::{self, Read, Seek, SeekFrom, Write};
2use std::ops::ControlFlow;
3
4use super::super::segment_path;
5use super::{AcquisitionError, AcquisitionWriter, EwfError, Result};
6
7#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
9pub enum UnreadableSectorPolicy {
10 #[default]
12 Stop,
13 ZeroFill,
17}
18
19#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct AcquisitionReadOptions {
23 pub retries: u32,
27 pub unreadable_sector_policy: UnreadableSectorPolicy,
29 pub maximum_error_ranges: usize,
33 pub checkpoint_interval: Option<u64>,
36}
37
38impl Default for AcquisitionReadOptions {
39 fn default() -> Self {
40 Self {
41 retries: 2,
42 unreadable_sector_policy: UnreadableSectorPolicy::Stop,
43 maximum_error_ranges: 65_536,
44 checkpoint_interval: None,
45 }
46 }
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51#[non_exhaustive]
52pub struct AcquisitionProgress {
53 pub source_size: u64,
55 pub bytes_written: u64,
57 pub checkpoint_bytes: u64,
59 pub sealed_segments: usize,
61 pub substituted_sectors: u64,
63 pub read_attempts: u64,
65 pub retry_attempts: u64,
67 pub read_offset: u64,
69 pub read_error: Option<io::ErrorKind>,
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75pub enum AcquisitionStatus {
76 Complete,
79 Cancelled,
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85#[non_exhaustive]
86pub struct AcquisitionOutcome {
87 pub status: AcquisitionStatus,
89 pub progress: AcquisitionProgress,
91}
92
93impl AcquisitionWriter {
94 pub fn acquire_from<R: Read + Seek>(
97 &mut self,
98 source: &mut R,
99 options: &AcquisitionReadOptions,
100 ) -> Result<AcquisitionOutcome> {
101 self.acquire_with_progress(source, options, |_| ControlFlow::Continue(()))
102 }
103
104 pub fn acquire_with_progress<R: Read + Seek>(
116 &mut self,
117 source: &mut R,
118 options: &AcquisitionReadOptions,
119 mut on_progress: impl FnMut(AcquisitionProgress) -> ControlFlow<()>,
120 ) -> Result<AcquisitionOutcome> {
121 self.ensure_healthy()?;
122 let sector_size = self.options.bytes_per_sector as usize;
123 if !self.offset.is_multiple_of(sector_size as u64)
124 || options.retries > 100
125 || options.maximum_error_ranges == 0
126 || self.errors.len() > options.maximum_error_ranges
127 || options.checkpoint_interval.is_some_and(|interval| {
128 interval == 0 || !interval.is_multiple_of(self.chunk_size as u64)
129 })
130 {
131 return Err(EwfError::Malformed(
132 "invalid acquisition read options or unaligned source position".into(),
133 ));
134 }
135 let maximum_segment_bytes = self.chunk_size as u64 * self.chunks_per_segment as u64;
139 let interval = options
140 .checkpoint_interval
141 .unwrap_or(maximum_segment_bytes)
142 .min(maximum_segment_bytes);
143 let remaining_segments = (self.source_size - self.checkpoint_offset()).div_ceil(interval);
144 let final_segment =
145 u64::try_from(self.sealed.len()).expect("segment count fits u64") + remaining_segments;
146 segment_path(
147 &self.first,
148 usize::try_from(final_segment)
149 .map_err(|_| EwfError::Unsupported("too many acquisition segments".into()))?,
150 )?;
151 let mut progress = self.source_progress();
152 let result = self.acquire_loop(source, options, &mut progress, &mut on_progress);
153 let status = match result {
154 Ok(()) => AcquisitionStatus::Complete,
155 Err(EwfError::Aborted) => AcquisitionStatus::Cancelled,
156 Err(error) => {
157 if !self.failed {
158 self.checkpoint()?;
159 }
160 return Err(error);
161 }
162 };
163 self.checkpoint()?;
164 self.refresh_source_progress(&mut progress);
165 Ok(AcquisitionOutcome { status, progress })
166 }
167
168 fn source_progress(&self) -> AcquisitionProgress {
169 let mut progress = AcquisitionProgress {
170 source_size: self.source_size,
171 bytes_written: 0,
172 checkpoint_bytes: 0,
173 sealed_segments: 0,
174 substituted_sectors: 0,
175 read_attempts: 0,
176 retry_attempts: 0,
177 read_offset: self.offset,
178 read_error: None,
179 };
180 self.refresh_source_progress(&mut progress);
181 progress
182 }
183
184 fn refresh_source_progress(&self, progress: &mut AcquisitionProgress) {
185 progress.bytes_written = self.offset;
186 progress.checkpoint_bytes = self.checkpoint_offset();
187 progress.sealed_segments = self.sealed.len();
188 progress.substituted_sectors = self.substituted_sectors;
189 }
190
191 fn acquire_loop<R: Read + Seek>(
192 &mut self,
193 source: &mut R,
194 options: &AcquisitionReadOptions,
195 progress: &mut AcquisitionProgress,
196 callback: &mut impl FnMut(AcquisitionProgress) -> ControlFlow<()>,
197 ) -> Result<()> {
198 notify(*progress, callback)?;
199 let mut buffer = vec![0; self.chunk_size];
200 let sector_size = self.options.bytes_per_sector as usize;
201 while self.offset < self.source_size {
202 let size = (self.chunk_size - self.pending.len())
203 .min((self.source_size - self.offset).min(usize::MAX as u64) as usize);
204 let read = if size > sector_size {
205 match read_attempt(source, &mut buffer[..size], self.offset, progress, callback) {
206 Ok(()) => true,
207 Err(AttemptError::Read(error)) if recoverable(&error) => false,
208 Err(error) => return Err(error.into_ewf()),
209 }
210 } else {
211 false
212 };
213 if read {
214 self.write_all(&buffer[..size])?;
215 self.after_source_write(options, progress, callback)?;
216 continue;
217 }
218 for sector in buffer[..size].chunks_mut(sector_size) {
221 let mut last_error = None;
222 for attempt in 0..=options.retries {
223 if attempt > 0 {
224 progress.retry_attempts += 1;
225 }
226 match read_attempt(source, sector, self.offset, progress, callback) {
227 Ok(()) => {
228 last_error = None;
229 break;
230 }
231 Err(AttemptError::Read(error)) if recoverable(&error) => {
232 last_error = Some(error);
233 }
234 Err(error) => return Err(error.into_ewf()),
235 }
236 }
237 if let Some(error) = last_error {
238 if options.unreadable_sector_policy == UnreadableSectorPolicy::Stop {
239 return Err(error.into());
240 }
241 self.record_substitution(options.maximum_error_ranges)?;
242 sector.fill(0);
243 }
244 self.write_all(sector)?;
245 self.after_source_write(options, progress, callback)?;
246 }
247 }
248 Ok(())
249 }
250
251 fn after_source_write(
252 &mut self,
253 options: &AcquisitionReadOptions,
254 progress: &mut AcquisitionProgress,
255 callback: &mut impl FnMut(AcquisitionProgress) -> ControlFlow<()>,
256 ) -> Result<()> {
257 if options
258 .checkpoint_interval
259 .is_some_and(|interval| self.offset - self.checkpoint_offset() >= interval)
260 {
261 self.checkpoint()?;
262 }
263 self.refresh_source_progress(progress);
264 notify(*progress, callback)
265 }
266
267 fn record_substitution(&mut self, maximum_ranges: usize) -> Result<()> {
268 let first_sector = self.offset / u64::from(self.options.bytes_per_sector);
269 if first_sector > u64::from(u32::MAX) {
272 return Err(EwfError::Unsupported(
273 "unreadable sector exceeds EWF1 error-table addressing".into(),
274 ));
275 }
276 if let Some(last) = self.errors.last_mut()
277 && last.first_sector + last.sector_count == first_sector
278 && last.sector_count < u64::from(u32::MAX)
279 {
280 last.sector_count += 1;
281 } else {
282 if self.errors.len() >= maximum_ranges {
283 return Err(EwfError::Unsupported(
284 "acquisition error-range limit reached".into(),
285 ));
286 }
287 self.errors.push(AcquisitionError {
288 first_sector,
289 sector_count: 1,
290 });
291 }
292 self.substituted_sectors += 1;
293 Ok(())
294 }
295}
296
297enum AttemptError {
298 Read(io::Error),
299 Fatal(EwfError),
300}
301
302impl AttemptError {
303 fn into_ewf(self) -> EwfError {
304 match self {
305 Self::Read(error) => error.into(),
306 Self::Fatal(error) => error,
307 }
308 }
309}
310
311fn read_attempt<R: Read + Seek>(
312 source: &mut R,
313 buffer: &mut [u8],
314 offset: u64,
315 progress: &mut AcquisitionProgress,
316 callback: &mut impl FnMut(AcquisitionProgress) -> ControlFlow<()>,
317) -> std::result::Result<(), AttemptError> {
318 progress.read_offset = offset;
319 progress.read_error = None;
320 notify(*progress, callback).map_err(AttemptError::Fatal)?;
321 let positioned = source
322 .seek(SeekFrom::Start(offset))
323 .map_err(|error| AttemptError::Fatal(error.into()))?;
324 if positioned != offset {
325 return Err(AttemptError::Fatal(EwfError::Malformed(
326 "source seek returned a different offset".into(),
327 )));
328 }
329 progress.read_attempts += 1;
330 let mut filled = 0;
331 while filled < buffer.len() {
332 let result = source.read(&mut buffer[filled..]);
333 let error = match result {
334 Ok(0) => Some(io::Error::new(
335 io::ErrorKind::UnexpectedEof,
336 "source ended before declared acquisition size",
337 )),
338 Ok(size) if size <= buffer.len() - filled => {
339 filled += size;
340 None
341 }
342 Ok(_) => Some(io::Error::new(
343 io::ErrorKind::InvalidInput,
344 "source returned an invalid read length",
345 )),
346 Err(error) => Some(error),
347 };
348 if let Some(error) = error {
349 progress.read_error = Some(error.kind());
350 notify(*progress, callback).map_err(AttemptError::Fatal)?;
351 return Err(AttemptError::Read(error));
352 }
353 notify(*progress, callback).map_err(AttemptError::Fatal)?;
354 }
355 Ok(())
356}
357
358fn recoverable(error: &io::Error) -> bool {
359 !matches!(
360 error.kind(),
361 io::ErrorKind::UnexpectedEof
362 | io::ErrorKind::PermissionDenied
363 | io::ErrorKind::InvalidInput
364 | io::ErrorKind::Unsupported
365 | io::ErrorKind::NotFound
366 | io::ErrorKind::Interrupted
367 | io::ErrorKind::TimedOut
368 | io::ErrorKind::WouldBlock
369 | io::ErrorKind::NotConnected
370 | io::ErrorKind::ConnectionAborted
371 | io::ErrorKind::ConnectionReset
372 | io::ErrorKind::BrokenPipe
373 )
374}
375
376fn notify(
377 progress: AcquisitionProgress,
378 callback: &mut impl FnMut(AcquisitionProgress) -> ControlFlow<()>,
379) -> Result<()> {
380 if callback(progress).is_break() {
381 Err(EwfError::Aborted)
382 } else {
383 Ok(())
384 }
385}