1#[cfg(feature = "rust173-polyfill")]
37use disk_core::rust173::IntIsMultipleOf as _;
38use std::io::{self, BufReader, Read, Seek, SeekFrom, Write};
39
40use byteorder::{BigEndian, ByteOrder};
41
42use crate::hfsplus::HfsPlusFilesystem;
43use crate::hfsplus_defrag::{stream_defragmented_hfsplus, DefragReport};
44use disk_core::filesystem::FilesystemError;
45
46#[derive(Debug, Clone)]
49pub struct WrappedHfsPlusInfo {
50 pub partition_offset: u64,
52 pub source_partition_size: u64,
54 pub al_block_size: u32,
57 pub al_block_start_sector: u16,
60 pub source_total_blocks: u16,
62 pub bitmap_start_sector: u16,
65 pub embed_sig: u16,
67 pub embed_start_block: u16,
70 pub embed_block_count: u16,
73 pub inner_offset: u64,
75 pub inner_size: u64,
77}
78
79pub fn detect_wrapped_hfsplus<R: Read + Seek>(
84 reader: &mut R,
85 partition_offset: u64,
86 partition_size: u64,
87) -> Option<WrappedHfsPlusInfo> {
88 reader.seek(SeekFrom::Start(partition_offset + 1024)).ok()?;
89 let mut mdb = [0u8; 512];
90 reader.read_exact(&mut mdb).ok()?;
91 parse_wrapper_mdb(&mdb, partition_offset, partition_size)
92}
93
94pub fn detect_wrapped_hfsplus_at(
104 file: &std::fs::File,
105 partition_offset: u64,
106 partition_size: u64,
107) -> Option<WrappedHfsPlusInfo> {
108 let mut mdb = [0u8; 512];
109 read_exact_at(file, partition_offset + 1024, &mut mdb).ok()?;
110 parse_wrapper_mdb(&mdb, partition_offset, partition_size)
111}
112
113fn read_exact_at(file: &std::fs::File, mut offset: u64, mut buf: &mut [u8]) -> io::Result<()> {
117 #[cfg(unix)]
118 use std::os::unix::fs::FileExt;
119 #[cfg(windows)]
120 use std::os::windows::fs::FileExt;
121 while !buf.is_empty() {
122 #[cfg(unix)]
123 let n = file.read_at(buf, offset)?;
124 #[cfg(windows)]
125 let n = file.seek_read(buf, offset)?;
126 if n == 0 {
127 return Err(io::Error::new(
128 io::ErrorKind::UnexpectedEof,
129 "unexpected EOF in positioned read",
130 ));
131 }
132 offset += n as u64;
133 let tmp = buf;
134 buf = &mut tmp[n..];
135 }
136 Ok(())
137}
138
139fn parse_wrapper_mdb(
145 mdb: &[u8; 512],
146 partition_offset: u64,
147 partition_size: u64,
148) -> Option<WrappedHfsPlusInfo> {
149 let sig = BigEndian::read_u16(&mdb[0..2]);
150 if sig != 0x4244 {
151 return None;
152 }
153 let embed_sig = BigEndian::read_u16(&mdb[124..126]);
154 if embed_sig != 0x482B && embed_sig != 0x4858 {
155 return None;
156 }
157 let al_block_size = BigEndian::read_u32(&mdb[20..24]);
158 let bitmap_start_sector = BigEndian::read_u16(&mdb[14..16]);
159 let total_blocks = BigEndian::read_u16(&mdb[18..20]);
160 let al_block_start_sector = BigEndian::read_u16(&mdb[28..30]);
161 let embed_start_block = BigEndian::read_u16(&mdb[126..128]);
162 let embed_block_count = BigEndian::read_u16(&mdb[128..130]);
163
164 if al_block_size == 0 || al_block_size % 512 != 0 {
165 return None;
166 }
167 if al_block_start_sector == 0 || bitmap_start_sector == 0 {
168 return None;
169 }
170 if embed_block_count == 0 {
171 return None;
172 }
173
174 let inner_offset = partition_offset
175 + (al_block_start_sector as u64) * 512
176 + (embed_start_block as u64) * (al_block_size as u64);
177 let inner_size = (embed_block_count as u64) * (al_block_size as u64);
178
179 let part_end = partition_offset.saturating_add(partition_size);
184 let inner_end = inner_offset.saturating_add(inner_size);
185 if inner_end > part_end {
186 return None;
187 }
188
189 Some(WrappedHfsPlusInfo {
190 partition_offset,
191 source_partition_size: partition_size,
192 al_block_size,
193 al_block_start_sector,
194 source_total_blocks: total_blocks,
195 bitmap_start_sector,
196 embed_sig,
197 embed_start_block,
198 embed_block_count,
199 inner_offset,
200 inner_size,
201 })
202}
203
204#[derive(Debug, Clone)]
207pub struct WrappedClonePlan {
208 pub info: WrappedHfsPlusInfo,
209 pub new_inner_size: u64,
212 pub new_embed_block_count: u16,
214 pub new_total_blocks: u16,
216 pub new_partition_size: u64,
218}
219
220#[derive(Debug, thiserror::Error)]
221pub enum WrappedCloneError {
222 #[error("inner HFS+ block size {0} is not a divisor of the wrapper allocation block size")]
223 InnerOuterBlockMismatch(u32),
224 #[error("requested inner target {requested} exceeds source extent {available}")]
225 TargetExceedsSource { requested: u64, available: u64 },
226 #[error("new wrapper allocation block count {count} exceeds the 16-bit HFS field limit")]
227 BlockCountOverflow { count: u64 },
228 #[error("io: {0}")]
229 Io(#[from] io::Error),
230 #[error("filesystem: {0}")]
231 Filesystem(#[from] FilesystemError),
232}
233
234pub fn plan_wrapped_clone(
241 info: &WrappedHfsPlusInfo,
242 target_inner_size_hint: u64,
243) -> Result<WrappedClonePlan, WrappedCloneError> {
244 let outer_bs = info.al_block_size as u64;
245 let new_inner_size = target_inner_size_hint.div_ceil(outer_bs) * outer_bs;
249 if new_inner_size > info.inner_size {
250 return Err(WrappedCloneError::TargetExceedsSource {
251 requested: new_inner_size,
252 available: info.inner_size,
253 });
254 }
255 let new_count_u64 = new_inner_size / outer_bs;
256 let new_embed_block_count: u16 =
257 new_count_u64
258 .try_into()
259 .map_err(|_| WrappedCloneError::BlockCountOverflow {
260 count: new_count_u64,
261 })?;
262 let new_total_blocks_u32 = info.embed_start_block as u32 + new_embed_block_count as u32;
263 let new_total_blocks: u16 =
264 new_total_blocks_u32
265 .try_into()
266 .map_err(|_| WrappedCloneError::BlockCountOverflow {
267 count: new_total_blocks_u32 as u64,
268 })?;
269 let new_partition_size =
273 (info.al_block_start_sector as u64) * 512 + (new_total_blocks as u64) * outer_bs + 1024;
274
275 Ok(WrappedClonePlan {
276 info: info.clone(),
277 new_inner_size,
278 new_embed_block_count,
279 new_total_blocks,
280 new_partition_size,
281 })
282}
283
284pub fn stream_wrapped_defragmented_hfsplus<R, W>(
291 reader: &mut R,
292 plan: &WrappedClonePlan,
293 dst: &mut W,
294 before_emit: Option<&dyn Fn()>,
295) -> Result<DefragReport, WrappedCloneError>
296where
297 R: Read + Seek + Send,
298 W: Write,
299{
300 let info = &plan.info;
301 let mut written: u64 = 0;
302 let outer_bs = info.al_block_size as u64;
303
304 copy_from_source(reader, info.partition_offset, dst, 1024)?;
306 written += 1024;
307
308 let mut mdb_buf = [0u8; 512];
310 {
311 reader.seek(SeekFrom::Start(info.partition_offset + 1024))?;
312 reader.read_exact(&mut mdb_buf)?;
313 }
314 patch_mdb_in_place(&mut mdb_buf, plan);
315 dst.write_all(&mdb_buf)?;
316 written += 512;
317
318 let bitmap_start = info.bitmap_start_sector as u64 * 512;
320 if bitmap_start < 1536 {
321 return Err(WrappedCloneError::Filesystem(FilesystemError::InvalidData(
323 format!("wrapper drVBMSt {} too low", info.bitmap_start_sector),
324 )));
325 }
326 if bitmap_start > 1536 {
327 copy_from_source(
328 reader,
329 info.partition_offset + 1536,
330 dst,
331 bitmap_start - 1536,
332 )?;
333 written += bitmap_start - 1536;
334 }
335
336 let alloc_start = info.al_block_start_sector as u64 * 512;
338 if alloc_start <= bitmap_start {
339 return Err(WrappedCloneError::Filesystem(FilesystemError::InvalidData(
340 format!(
341 "wrapper drAlBlSt {} not greater than drVBMSt {}",
342 info.al_block_start_sector, info.bitmap_start_sector,
343 ),
344 )));
345 }
346 let bitmap_len = alloc_start - bitmap_start;
347 let mut bitmap = vec![0u8; bitmap_len as usize];
348 {
349 reader.seek(SeekFrom::Start(info.partition_offset + bitmap_start))?;
350 reader.read_exact(&mut bitmap)?;
351 }
352 clear_bitmap_tail(&mut bitmap, plan.new_total_blocks);
353 dst.write_all(&bitmap)?;
354 written += bitmap_len;
355
356 let pre_embed_bytes = (info.embed_start_block as u64) * outer_bs;
358 if pre_embed_bytes > 0 {
359 copy_from_source(
360 reader,
361 info.partition_offset + alloc_start,
362 dst,
363 pre_embed_bytes,
364 )?;
365 written += pre_embed_bytes;
366 }
367
368 let inner_clone_reader = clone_reader(reader)?;
375 let mut inner_fs =
376 HfsPlusFilesystem::open(BufReader::new(inner_clone_reader), info.inner_offset)
377 .map_err(WrappedCloneError::Filesystem)?;
378
379 let inner_bs = inner_fs.block_size();
384 if !plan.new_inner_size.is_multiple_of(inner_bs as u64) {
385 return Err(WrappedCloneError::InnerOuterBlockMismatch(inner_bs));
386 }
387
388 let report = stream_defragmented_hfsplus(&mut inner_fs, plan.new_inner_size, dst, before_emit)
389 .map_err(WrappedCloneError::Filesystem)?;
390 written += plan.new_inner_size;
391
392 let alt_mdb_offset = plan.new_partition_size - 1024;
394 if written > alt_mdb_offset {
395 return Err(WrappedCloneError::Filesystem(FilesystemError::InvalidData(
396 format!(
397 "wrapper clone over-emitted: wrote {written} bytes but alt MDB sits at {alt_mdb_offset}"
398 ),
399 )));
400 }
401 write_zeros(dst, alt_mdb_offset - written)?;
402 written = alt_mdb_offset;
403
404 dst.write_all(&mdb_buf)?;
406 written += 512;
407
408 write_zeros(dst, 512)?;
410 written += 512;
411
412 debug_assert_eq!(written, plan.new_partition_size);
413 Ok(report)
414}
415
416fn patch_mdb_in_place(mdb: &mut [u8; 512], plan: &WrappedClonePlan) {
417 BigEndian::write_u16(&mut mdb[18..20], plan.new_total_blocks);
419 BigEndian::write_u16(&mut mdb[34..36], 0);
423 BigEndian::write_u16(&mut mdb[128..130], plan.new_embed_block_count);
425 }
428
429fn clear_bitmap_tail(bitmap: &mut [u8], new_total_blocks: u16) {
430 let n = new_total_blocks as usize;
431 let byte_idx = n / 8;
432 let bit_idx = n % 8;
433 if byte_idx >= bitmap.len() {
434 return;
435 }
436 if bit_idx > 0 {
440 let keep_mask: u8 = !((1u8 << (8 - bit_idx)) - 1);
442 bitmap[byte_idx] &= keep_mask;
443 } else {
444 bitmap[byte_idx] = 0;
445 }
446 for byte in bitmap[byte_idx + 1..].iter_mut() {
447 *byte = 0;
448 }
449}
450
451const ZEROS: &[u8] = &[0u8; 65536];
452
453fn write_zeros<W: Write>(dst: &mut W, mut remaining: u64) -> io::Result<()> {
454 while remaining > 0 {
455 let n = remaining.min(ZEROS.len() as u64) as usize;
456 dst.write_all(&ZEROS[..n])?;
457 remaining -= n as u64;
458 }
459 Ok(())
460}
461
462fn copy_from_source<R: Read + Seek, W: Write>(
463 reader: &mut R,
464 src_offset: u64,
465 dst: &mut W,
466 len: u64,
467) -> io::Result<()> {
468 reader.seek(SeekFrom::Start(src_offset))?;
469 let mut buf = [0u8; 65536];
470 let mut remaining = len;
471 while remaining > 0 {
472 let n = remaining.min(buf.len() as u64) as usize;
473 reader.read_exact(&mut buf[..n])?;
474 dst.write_all(&buf[..n])?;
475 remaining -= n as u64;
476 }
477 Ok(())
478}
479
480fn clone_reader<R: Read + Seek>(reader: &mut R) -> Result<ReaderClone<'_, R>, WrappedCloneError> {
486 Ok(ReaderClone {
493 inner: reader,
497 })
498}
499
500struct ReaderClone<'a, R> {
502 inner: &'a mut R,
503}
504
505impl<'a, R: Read> Read for ReaderClone<'a, R> {
506 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
507 self.inner.read(buf)
508 }
509}
510
511impl<'a, R: Seek> Seek for ReaderClone<'a, R> {
512 fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
513 self.inner.seek(pos)
514 }
515}
516
517#[cfg(test)]
518mod tests {
519 use super::*;
520 use std::io::Cursor;
521
522 use crate::hfsplus::create_blank_hfsplus;
523
524 fn build_wrapped_test_image(
535 inner_block_size: u32,
536 inner_size: u64,
537 outer_al_block_size: u32,
538 embed_start_block: u16,
539 ) -> (Vec<u8>, u64) {
540 assert_eq!(inner_size % outer_al_block_size as u64, 0);
541 let inner = create_blank_hfsplus(inner_size, inner_block_size, "Inner", false);
542 assert_eq!(inner.len() as u64, inner_size);
543
544 let outer_bs = outer_al_block_size as u64;
545 let embed_block_count = (inner_size / outer_bs) as u16;
546 let total_blocks: u16 = embed_start_block + embed_block_count;
547 let bitmap_bits = total_blocks as u64;
548 let bitmap_bytes = bitmap_bits.div_ceil(8);
549 let bitmap_sectors = bitmap_bytes.div_ceil(512) as u16;
550 let drvbmst: u16 = 3;
551 let dralblst: u16 = drvbmst + bitmap_sectors;
552
553 let alloc_start_bytes = (dralblst as u64) * 512;
554 let inner_offset = alloc_start_bytes + (embed_start_block as u64) * outer_bs;
555 let partition_size = alloc_start_bytes + (total_blocks as u64) * outer_bs + 1024;
556
557 let mut img = vec![0u8; partition_size as usize];
558
559 let mdb_off = 1024;
561 let mdb = &mut img[mdb_off..mdb_off + 512];
562 BigEndian::write_u16(&mut mdb[0..2], 0x4244);
563 BigEndian::write_u16(&mut mdb[14..16], drvbmst);
564 BigEndian::write_u16(&mut mdb[18..20], total_blocks); BigEndian::write_u32(&mut mdb[20..24], outer_al_block_size); BigEndian::write_u16(&mut mdb[28..30], dralblst); BigEndian::write_u16(&mut mdb[34..36], 0); BigEndian::write_u16(&mut mdb[124..126], 0x482B); BigEndian::write_u16(&mut mdb[126..128], embed_start_block);
570 BigEndian::write_u16(&mut mdb[128..130], embed_block_count);
571
572 let bitmap_off = (drvbmst as usize) * 512;
574 for bit in 0..(total_blocks as usize) {
575 img[bitmap_off + bit / 8] |= 0x80 >> (bit % 8);
576 }
577
578 img[inner_offset as usize..(inner_offset + inner_size) as usize].copy_from_slice(&inner);
580
581 let alt_off = partition_size as usize - 1024;
583 let mdb_copy = img[mdb_off..mdb_off + 512].to_vec();
584 img[alt_off..alt_off + 512].copy_from_slice(&mdb_copy);
585
586 (img, partition_size)
587 }
588
589 #[test]
590 fn detect_recognises_synthesised_wrapper() {
591 let (img, partition_size) = build_wrapped_test_image(4096, 4 * 1024 * 1024, 4096, 1);
592 let mut cur = Cursor::new(&img);
593 let info =
594 detect_wrapped_hfsplus(&mut cur, 0, partition_size).expect("should detect wrapper");
595 assert_eq!(info.embed_sig, 0x482B);
596 assert_eq!(info.al_block_size, 4096);
597 assert_eq!(info.embed_start_block, 1);
598 assert_eq!(info.embed_block_count as u32, (4 * 1024 * 1024) / 4096);
599 assert_eq!(info.inner_size, 4 * 1024 * 1024);
600 }
601
602 #[test]
603 fn round_trip_clones_at_source_size() {
604 let (img, partition_size) = build_wrapped_test_image(4096, 4 * 1024 * 1024, 4096, 1);
606 let mut cur = Cursor::new(img.clone());
607 let info = detect_wrapped_hfsplus(&mut cur, 0, partition_size).unwrap();
608 let plan = plan_wrapped_clone(&info, info.inner_size).unwrap();
610 assert_eq!(plan.new_inner_size, info.inner_size);
611 assert_eq!(plan.new_partition_size, partition_size);
612
613 let mut out: Vec<u8> = Vec::with_capacity(partition_size as usize);
614 let mut src_cur = Cursor::new(img.as_slice());
615 let report = stream_wrapped_defragmented_hfsplus(&mut src_cur, &plan, &mut out, None)
616 .expect("clone should succeed");
617 assert_eq!(report.files_copied, 0);
619 assert_eq!(out.len(), partition_size as usize);
620
621 let mut out_cur = Cursor::new(out.as_slice());
623 let info2 = detect_wrapped_hfsplus(&mut out_cur, 0, partition_size)
624 .expect("output must still parse as wrapper");
625 assert_eq!(info2.embed_sig, info.embed_sig);
626 assert_eq!(info2.embed_start_block, info.embed_start_block);
627 assert_eq!(info2.embed_block_count, info.embed_block_count);
628
629 let mut inner_cur = Cursor::new(out.as_slice());
631 let _hfs = crate::hfsplus::HfsPlusFilesystem::open(&mut inner_cur, info2.inner_offset)
632 .expect("inner HFS+ must be valid after wrapper round-trip");
633 }
634
635 #[test]
636 fn shrink_keeps_wrapper_and_inner_valid() {
637 let (img, partition_size) = build_wrapped_test_image(4096, 8 * 1024 * 1024, 4096, 1);
640 let mut cur = Cursor::new(img.clone());
641 let info = detect_wrapped_hfsplus(&mut cur, 0, partition_size).unwrap();
642
643 let target_inner = 1024 * 1024u64;
644 let plan = plan_wrapped_clone(&info, target_inner).unwrap();
645 assert_eq!(plan.new_inner_size, target_inner);
646 assert!(plan.new_partition_size < partition_size);
647
648 let mut out: Vec<u8> = Vec::with_capacity(plan.new_partition_size as usize);
649 let mut src_cur = Cursor::new(img.as_slice());
650 stream_wrapped_defragmented_hfsplus(&mut src_cur, &plan, &mut out, None)
651 .expect("shrink clone should succeed");
652 assert_eq!(out.len(), plan.new_partition_size as usize);
653
654 let mut out_cur = Cursor::new(out.as_slice());
656 let info2 = detect_wrapped_hfsplus(&mut out_cur, 0, plan.new_partition_size).unwrap();
657 assert_eq!(
658 info2.embed_block_count, plan.new_embed_block_count,
659 "wrapper must reflect the new embedded extent size"
660 );
661 let mut inner_cur = Cursor::new(out.as_slice());
662 let _hfs = crate::hfsplus::HfsPlusFilesystem::open(&mut inner_cur, info2.inner_offset)
663 .expect("inner HFS+ must be valid after wrapper shrink");
664 }
665
666 #[test]
667 fn clear_bitmap_tail_zeros_past_boundary() {
668 let mut bm = vec![0xFFu8; 4];
670 clear_bitmap_tail(&mut bm, 10);
671 assert_eq!(bm[0], 0xFF);
673 assert_eq!(bm[1], 0b1100_0000);
674 assert_eq!(bm[2], 0x00);
675 assert_eq!(bm[3], 0x00);
676 }
677
678 #[test]
679 fn clear_bitmap_tail_byte_boundary() {
680 let mut bm = vec![0xFFu8; 4];
681 clear_bitmap_tail(&mut bm, 16);
682 assert_eq!(bm[0], 0xFF);
683 assert_eq!(bm[1], 0xFF);
684 assert_eq!(bm[2], 0x00);
685 assert_eq!(bm[3], 0x00);
686 }
687
688 #[test]
689 fn plan_rounds_inner_to_outer_block() {
690 let info = WrappedHfsPlusInfo {
691 partition_offset: 0,
692 source_partition_size: 1024 * 1024 * 1024, al_block_size: 32 * 1024, al_block_start_sector: 19,
695 source_total_blocks: 32000,
696 bitmap_start_sector: 3,
697 embed_sig: 0x482B,
698 embed_start_block: 8,
699 embed_block_count: 30000,
700 inner_offset: 19 * 512 + 8 * 32 * 1024,
701 inner_size: 30000u64 * 32 * 1024,
702 };
703 let hint = 5 * 1024 * 1024 + 17; let plan = plan_wrapped_clone(&info, hint).unwrap();
706 assert_eq!(plan.new_inner_size % (32 * 1024), 0);
707 assert!(plan.new_inner_size >= hint);
708 assert_eq!(
710 plan.new_total_blocks as u64,
711 info.embed_start_block as u64 + plan.new_embed_block_count as u64
712 );
713 let expected_size = 19u64 * 512 + (plan.new_total_blocks as u64) * 32 * 1024 + 1024;
715 assert_eq!(plan.new_partition_size, expected_size);
716 }
717}