ewf_image/writer/acquisition/
checkpoint.rs1use std::fs;
2use std::ops::ControlFlow;
3use std::path::{Path, PathBuf};
4
5use super::{
6 AcquisitionOptions, AcquisitionWriter, Seal, WriteOptions, checkpoint_count, checkpoint_path,
7 ensure_output_absent, fingerprint, normalize_errors, normalize_first, read_fixed,
8 require_directory, require_file_type, staged_path, validate_segment_budget,
9};
10use crate::publication::{OutputLock, acquisition_path};
11use crate::{AcquisitionError, EwfError, Image, Result};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15#[non_exhaustive]
16pub enum AcquisitionOperationPhase {
17 ValidatingSegments,
19 RehashingMedia,
21 Publishing,
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30#[non_exhaustive]
31pub struct AcquisitionOperationProgress {
32 pub phase: AcquisitionOperationPhase,
34 pub bytes_processed: u64,
37 pub bytes_total: u64,
39 pub segments_processed: usize,
41 pub segments_total: usize,
43}
44
45#[derive(Debug, Clone, PartialEq, Eq)]
51#[non_exhaustive]
52pub struct AcquisitionCheckpoint {
53 pub source_size: u64,
55 pub checkpoint_bytes: u64,
57 pub chunk_size: u64,
59 pub sealed_segments: usize,
61 pub stored_bytes: u64,
63 pub ready_to_finish: bool,
65 pub publication_started: bool,
67 pub segment_hashes_validated: bool,
70 pub acquisition_errors: Vec<AcquisitionError>,
72 pub substituted_sectors: u64,
74}
75
76pub(super) struct CheckpointData {
77 pub(super) state: PathBuf,
78 pub(super) sealed: Vec<Seal>,
79 pub(super) report: AcquisitionCheckpoint,
80 pub(super) image: Option<Image>,
81}
82
83impl AcquisitionWriter {
84 pub fn inspect_checkpoint(
88 first: impl AsRef<Path>,
89 options: &AcquisitionOptions,
90 source_identity: [u8; 32],
91 ) -> Result<AcquisitionCheckpoint> {
92 inspect(first.as_ref(), options, source_identity, false, &mut |_| {
93 ControlFlow::Continue(())
94 })
95 }
96
97 pub fn validate_checkpoint(
101 first: impl AsRef<Path>,
102 options: &AcquisitionOptions,
103 source_identity: [u8; 32],
104 mut on_progress: impl FnMut(AcquisitionOperationProgress) -> ControlFlow<()>,
105 ) -> Result<AcquisitionCheckpoint> {
106 inspect(
107 first.as_ref(),
108 options,
109 source_identity,
110 true,
111 &mut on_progress,
112 )
113 }
114}
115
116fn inspect(
117 first: &Path,
118 options: &AcquisitionOptions,
119 source_identity: [u8; 32],
120 validate: bool,
121 callback: &mut impl FnMut(AcquisitionOperationProgress) -> ControlFlow<()>,
122) -> Result<AcquisitionCheckpoint> {
123 let write_options = options.writer_options()?;
124 let first = normalize_first(first)?;
125 validate_segment_budget(&first, options)?;
126 let _lock = OutputLock::acquire(&first)?;
127 Ok(load_checkpoint(
128 &first,
129 options,
130 &write_options,
131 source_identity,
132 validate,
133 callback,
134 )?
135 .report)
136}
137
138pub(super) fn load_checkpoint(
139 first: &Path,
140 options: &AcquisitionOptions,
141 write_options: &WriteOptions,
142 source_identity: [u8; 32],
143 validate: bool,
144 callback: &mut impl FnMut(AcquisitionOperationProgress) -> ControlFlow<()>,
145) -> Result<CheckpointData> {
146 crate::publication::ensure_no_publication(first)?;
147 let state = acquisition_path(first)?;
148 require_directory(&state)?;
149 require_directory(&state.join("scratch"))?;
150 if read_fixed::<32>(&state.join("config"))?
151 != fingerprint(first, options, write_options, source_identity)?
152 {
153 return Err(EwfError::Malformed(
154 "acquisition identity or options changed".into(),
155 ));
156 }
157 let count = checkpoint_count(&state)?;
158 let mut sealed = Vec::with_capacity(count);
159 let mut paths = Vec::with_capacity(count);
160 let mut previous = 0;
161 let mut stored_bytes = 0_u64;
162 let chunk_size = u64::from(options.bytes_per_sector) * u64::from(options.sectors_per_chunk);
163 for index in 1..=count {
164 let seal = Seal::read(&checkpoint_path(&state, index))?;
165 if seal.end <= previous
166 || seal.end > options.source_size
167 || (seal.end < options.source_size && !seal.end.is_multiple_of(chunk_size))
168 || (seal.end - previous).div_ceil(chunk_size) > u64::from(options.chunks_per_segment)
169 {
170 return Err(EwfError::Malformed(
171 "invalid acquisition checkpoint geometry".into(),
172 ));
173 }
174 let path = staged_path(first, &state, index)?;
175 let metadata = fs::symlink_metadata(&path)?;
176 require_file_type(&metadata)?;
177 if metadata.len() != seal.size {
178 return Err(EwfError::Malformed(
179 "sealed acquisition segment changed".into(),
180 ));
181 }
182 stored_bytes = stored_bytes
183 .checked_add(seal.size)
184 .ok_or_else(|| EwfError::Malformed("acquisition stored-size overflow".into()))?;
185 previous = seal.end;
186 paths.push(path);
187 sealed.push(seal);
188 }
189 let publishing = state.join("publishing").try_exists()?;
190 if publishing {
191 read_fixed::<0>(&state.join("publishing"))?;
192 if previous != options.source_size {
193 return Err(EwfError::Malformed(
194 "incomplete acquisition publication".into(),
195 ));
196 }
197 } else {
198 ensure_output_absent(first)?;
199 }
200 if validate {
201 let mut progress = operation_progress(
202 AcquisitionOperationPhase::ValidatingSegments,
203 stored_bytes,
204 count,
205 );
206 notify(progress, callback)?;
207 for (path, seal) in paths.iter().zip(&sealed) {
208 seal.validate_with_progress(path, &mut progress, callback)?;
209 }
210 }
211 let (image, errors) = if paths.is_empty() {
212 (None, Vec::new())
213 } else {
214 let image = Image::open_acquisition_prefix(paths, options.source_size, previous)?;
215 if image.chunk_size() != chunk_size
216 || image.info().acquisition_complete != (previous == options.source_size)
217 {
218 return Err(EwfError::Malformed(
219 "acquisition segment geometry changed".into(),
220 ));
221 }
222 let errors = normalize_errors(
223 image.acquisition_errors(),
224 previous,
225 options.bytes_per_sector,
226 )?;
227 (Some(image), errors)
228 };
229 let report = AcquisitionCheckpoint {
230 source_size: options.source_size,
231 checkpoint_bytes: previous,
232 chunk_size,
233 sealed_segments: count,
234 stored_bytes,
235 ready_to_finish: previous == options.source_size,
236 publication_started: publishing,
237 segment_hashes_validated: validate,
238 substituted_sectors: errors.iter().map(|range| range.sector_count).sum(),
239 acquisition_errors: errors,
240 };
241 Ok(CheckpointData {
242 state,
243 sealed,
244 report,
245 image,
246 })
247}
248
249pub(super) fn operation_progress(
250 phase: AcquisitionOperationPhase,
251 total: u64,
252 segments: usize,
253) -> AcquisitionOperationProgress {
254 AcquisitionOperationProgress {
255 phase,
256 bytes_processed: 0,
257 bytes_total: total,
258 segments_processed: 0,
259 segments_total: segments,
260 }
261}
262
263pub(super) fn notify(
264 progress: AcquisitionOperationProgress,
265 callback: &mut impl FnMut(AcquisitionOperationProgress) -> ControlFlow<()>,
266) -> Result<()> {
267 if callback(progress).is_break() {
268 Err(EwfError::Aborted)
269 } else {
270 Ok(())
271 }
272}