1#[cfg(target_endian = "big")]
6assert!(false, "This library assumes little-endian!");
7
8pub mod builder;
9mod format;
10pub mod reader;
11
12use crate::format::{CHUNK_HEADER_SIZE, ChunkHeader, SPARSE_HEADER_SIZE, SparseHeader};
13use crate::reader::SparseReader;
14
15use core::fmt;
16use serde::de::DeserializeOwned;
17use thiserror::Error;
18
19use std::fs::File;
20use std::io::{Cursor, Read, Seek, SeekFrom, Write};
21use std::path::Path;
22use tempfile::{NamedTempFile, TempPath};
23#[cfg(target_os = "fuchsia")]
24use zx;
25
26const BLK_SIZE: u32 = 0x1000;
29
30#[derive(Debug, Clone, Copy)]
31pub enum SparseDataType {
32 Header,
33 ChunkHeader,
34 FillValue,
35 Checksum,
36}
37
38impl std::fmt::Display for SparseDataType {
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 match self {
41 Self::Header => write!(f, "header"),
42 Self::ChunkHeader => write!(f, "chunk header"),
43 Self::FillValue => write!(f, "fill value"),
44 Self::Checksum => write!(f, "checksum"),
45 }
46 }
47}
48
49#[derive(Debug, Error)]
50pub enum DeserializeError {
51 #[error("IO error: {0}")]
52 Io(#[from] std::io::Error),
53
54 #[error("Bincode error: {0}")]
55 Bincode(#[from] Box<bincode::ErrorKind>),
56}
57
58#[derive(Debug, Clone, Copy, thiserror::Error)]
59pub enum UnalignedSource {
60 #[error("buffer length {0}")]
61 Buffer(usize),
62
63 #[error("Reader length {0}")]
64 Reader(u64),
65
66 #[error("Skip length {0}")]
67 Skip(u64),
68
69 #[error("Fill length {0}")]
70 Fill(u64),
71
72 #[error("Vmo size {0}")]
73 Vmo(u64),
74}
75
76#[derive(Debug, Error)]
77pub enum SparseError {
78 #[error("IO error: {0}")]
79 Io(#[from] std::io::Error),
80
81 #[error("Failed to deserialize {ty}: {source}")]
82 Deserialize {
83 ty: SparseDataType,
84 #[source]
85 source: DeserializeError,
86 },
87
88 #[error("Failed to serialize {ty}: {source}")]
89 Serialize {
90 ty: SparseDataType,
91 #[source]
92 source: Box<bincode::ErrorKind>,
93 },
94
95 #[error("Invalid sparse image header")]
96 InvalidHeader,
97
98 #[error("Invalid chunk header")]
99 InvalidChunkHeader,
100
101 #[error("Invalid chunk type {0}")]
102 InvalidChunkType(u16),
103
104 #[error("Given maximum download size ({0}) is less than the block size ({1})")]
105 MaxDownloadSizeTooSmall(u64, u32),
106
107 #[error("No source for Raw chunk")]
108 NoSourceForRawChunk,
109
110 #[error("Chunk is not block aligned")]
111 UnalignedChunk,
112
113 #[error("Failed to copy contents: {0}")]
114 CopyContents(#[source] std::io::Error),
115
116 #[error("Failed to fill contents: {0}")]
117 FillContents(#[source] std::io::Error),
118
119 #[error("Failed to skip contents: {0}")]
120 SkipContents(#[source] std::io::Error),
121
122 #[cfg(target_os = "fuchsia")]
123 #[error("Zircon error: {0}")]
124 Zircon(#[from] zx::Status),
125
126 #[error("Invalid {0}")]
127 UnalignedDataSource(UnalignedSource),
128
129 #[error("Sparse image would contain too many blocks")]
130 TooManyBlocks,
131}
132
133fn deserialize_from<'a, T: DeserializeOwned, R: Read + ?Sized>(
134 source: &mut R,
135) -> Result<T, DeserializeError> {
136 let mut buf = vec![0u8; std::mem::size_of::<T>()];
137 source.read_exact(&mut buf[..])?;
138 bincode::deserialize(&buf[..]).map_err(Into::into)
139}
140
141pub trait Writer: Write + Seek {
143 fn set_len(&mut self, size: u64) -> Result<(), SparseError>;
145}
146
147impl Writer for File {
148 fn set_len(&mut self, size: u64) -> Result<(), SparseError> {
149 File::set_len(self, size).map_err(SparseError::from)
150 }
151}
152
153impl Writer for Cursor<Vec<u8>> {
154 fn set_len(&mut self, size: u64) -> Result<(), SparseError> {
155 Vec::resize(self.get_mut(), size as usize, 0u8);
156 Ok(())
157 }
158}
159
160struct LimitedReader<'a, R>(pub &'a mut R, pub usize);
166
167impl<'a, R: Read + Seek> Read for LimitedReader<'a, R> {
168 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
169 let offset = self.0.stream_position()?;
170 let avail = self.1.saturating_sub(offset as usize);
171 let to_read = std::cmp::min(avail, buf.len());
172 self.0.read(&mut buf[..to_read])
173 }
174}
175
176pub fn is_sparse_image<R: Read + Seek>(reader: &mut R) -> bool {
178 || -> Option<bool> {
179 let header: SparseHeader = deserialize_from(reader).ok()?;
180 let is_sparse = header.magic == format::SPARSE_HEADER_MAGIC;
181 reader.seek(SeekFrom::Start(0)).ok()?;
182 Some(is_sparse)
183 }()
184 .unwrap_or(false)
185}
186
187#[derive(Clone, PartialEq, Debug)]
188pub enum Chunk {
189 Raw { start: u64, size: u64 },
193 Fill { start: u64, size: u64, value: u32 },
197 DontCare { start: u64, size: u64 },
204 #[allow(dead_code)]
208 Crc32 { checksum: u32 },
209}
210
211impl Chunk {
212 pub fn read_metadata<R: Read>(
217 reader: &mut R,
218 offset: u64,
219 block_size: u32,
220 ) -> Result<Self, SparseError> {
221 let header: ChunkHeader = deserialize_from(reader)
222 .map_err(|e| SparseError::Deserialize { ty: SparseDataType::ChunkHeader, source: e })?;
223 if !header.valid() {
224 return Err(SparseError::InvalidChunkHeader);
225 }
226
227 let size = header.chunk_sz as u64 * block_size as u64;
228 match header.chunk_type {
229 format::CHUNK_TYPE_RAW => Ok(Self::Raw { start: offset, size }),
230 format::CHUNK_TYPE_FILL => {
231 let value: u32 = deserialize_from(reader).map_err(|e| {
232 SparseError::Deserialize { ty: SparseDataType::FillValue, source: e }
233 })?;
234 Ok(Self::Fill { start: offset, size, value })
235 }
236 format::CHUNK_TYPE_DONT_CARE => Ok(Self::DontCare { start: offset, size }),
237 format::CHUNK_TYPE_CRC32 => {
238 let checksum: u32 = deserialize_from(reader).map_err(|e| {
239 SparseError::Deserialize { ty: SparseDataType::Checksum, source: e }
240 })?;
241 Ok(Self::Crc32 { checksum })
242 }
243 _ => unreachable!(),
245 }
246 }
247
248 fn valid(&self, block_size: u32) -> bool {
249 self.output_size() % (block_size as u64) == 0
250 }
251
252 fn output_offset(&self) -> Option<u64> {
255 match self {
256 Self::Raw { start, .. } => Some(*start),
257 Self::Fill { start, .. } => Some(*start),
258 Self::DontCare { start, .. } => Some(*start),
259 Self::Crc32 { .. } => None,
260 }
261 }
262
263 fn output_size(&self) -> u64 {
265 match self {
266 Self::Raw { size, .. } => *size,
267 Self::Fill { size, .. } => *size,
268 Self::DontCare { size, .. } => *size,
269 Self::Crc32 { .. } => 0,
270 }
271 }
272
273 fn output_blocks(&self, block_size: u32) -> u32 {
275 self.output_size().div_ceil(block_size as u64) as u32
276 }
277
278 fn chunk_type(&self) -> u16 {
281 match self {
282 Self::Raw { .. } => format::CHUNK_TYPE_RAW,
283 Self::Fill { .. } => format::CHUNK_TYPE_FILL,
284 Self::DontCare { .. } => format::CHUNK_TYPE_DONT_CARE,
285 Self::Crc32 { .. } => format::CHUNK_TYPE_CRC32,
286 }
287 }
288
289 fn chunk_data_len(&self) -> u32 {
296 let header_size = format::CHUNK_HEADER_SIZE;
297 let data_size = match self {
298 Self::Raw { size, .. } => *size as u32,
299 Self::Fill { .. } => std::mem::size_of::<u32>() as u32,
300 Self::DontCare { .. } => 0,
301 Self::Crc32 { .. } => std::mem::size_of::<u32>() as u32,
302 };
303 header_size.checked_add(data_size).unwrap()
304 }
305
306 fn write<W: Write, R: Read>(
310 &self,
311 source: Option<&mut R>,
312 dest: &mut W,
313 block_size: u32,
314 ) -> Result<(), SparseError> {
315 if !self.valid(block_size) {
316 return Err(SparseError::UnalignedChunk);
317 }
318 let header = ChunkHeader::new(
319 self.chunk_type(),
320 0x0,
321 self.output_blocks(block_size),
322 self.chunk_data_len(),
323 );
324
325 bincode::serialize_into(&mut *dest, &header)
326 .map_err(|e| SparseError::Serialize { ty: SparseDataType::ChunkHeader, source: e })?;
327
328 match self {
329 Self::Raw { size, .. } => {
330 if source.is_none() {
331 return Err(SparseError::NoSourceForRawChunk);
332 }
333 let n = std::io::copy(source.unwrap(), dest)?;
334 let size = *size as u64;
335 if n < size {
336 let zeroes = vec![0u8; (size - n) as usize];
337 dest.write_all(&zeroes)?;
338 }
339 }
340 Self::Fill { value, .. } => {
341 bincode::serialize_into(dest, value).map_err(|e| SparseError::Serialize {
343 ty: SparseDataType::FillValue,
344 source: e,
345 })?;
346 }
347 Self::DontCare { .. } => {
348 }
350 Self::Crc32 { checksum } => {
351 bincode::serialize_into(dest, checksum).map_err(|e| SparseError::Serialize {
352 ty: SparseDataType::Checksum,
353 source: e,
354 })?;
355 }
356 }
357 Ok(())
358 }
359}
360
361impl fmt::Display for Chunk {
362 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
363 let message = match self {
364 Self::Raw { start, size } => {
365 format!("RawChunk: start: {}, total bytes: {}", start, size)
366 }
367 Self::Fill { start, size, value } => {
368 format!("FillChunk: start: {}, value: {}, n_blocks: {}", start, value, size)
369 }
370 Self::DontCare { start, size } => {
371 format!("DontCareChunk: start: {}, bytes: {}", start, size)
372 }
373 Self::Crc32 { checksum } => format!("Crc32Chunk: checksum: {:?}", checksum),
374 };
375 write!(f, "{}", message)
376 }
377}
378
379pub const NO_SOURCE: Option<&mut Cursor<&[u8]>> = None;
383
384#[derive(Clone, Debug, PartialEq)]
385struct SparseFileWriter {
386 chunks: Vec<Chunk>,
387}
388
389impl SparseFileWriter {
390 fn new(chunks: Vec<Chunk>) -> SparseFileWriter {
391 SparseFileWriter { chunks }
392 }
393
394 fn total_blocks(&self) -> u32 {
395 self.chunks.iter().map(|c| c.output_blocks(BLK_SIZE)).sum()
396 }
397
398 fn total_bytes(&self) -> u64 {
399 self.chunks.iter().map(|c| c.output_size() as u64).sum()
400 }
401
402 fn write<W: Write + Seek, R: Read + Seek>(
403 &self,
404 reader: &mut R,
405 writer: &mut W,
406 ) -> Result<(), SparseError> {
407 let header = SparseHeader::new(
408 BLK_SIZE.try_into().unwrap(), self.total_blocks(), self.chunks.len().try_into().unwrap(), );
412
413 bincode::serialize_into(&mut *writer, &header)
414 .map_err(|e| SparseError::Serialize { ty: SparseDataType::Header, source: e })?;
415
416 for chunk in &self.chunks {
417 let mut reader = if let &Chunk::Raw { start, size } = chunk {
418 reader.seek(SeekFrom::Start(start))?;
419 Some(LimitedReader(reader, start as usize + size as usize))
420 } else {
421 None
422 };
423 chunk.write(reader.as_mut(), writer, BLK_SIZE)?;
424 }
425
426 Ok(())
427 }
428}
429
430impl fmt::Display for SparseFileWriter {
431 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
432 write!(f, r"SparseFileWriter: {} Chunks:", self.chunks.len())
433 }
434}
435
436fn add_sparse_chunk(r: &mut Vec<Chunk>, chunk: Chunk) -> Result<(), SparseError> {
445 match r.last_mut() {
446 Some(last) => match (&last, &chunk) {
449 (Chunk::Raw { start, size }, Chunk::Raw { size: new_length, .. })
450 if size.checked_add(*new_length).is_some() =>
451 {
452 *last = Chunk::Raw { start: *start, size: size + new_length };
453 return Ok(());
454 }
455 (
456 Chunk::Fill { start, size, value },
457 Chunk::Fill { size: new_size, value: new_value, .. },
458 ) if value == new_value && size.checked_add(*new_size).is_some() => {
459 *last = Chunk::Fill { start: *start, size: size + new_size, value: *value };
460 return Ok(());
461 }
462 (Chunk::DontCare { start, size }, Chunk::DontCare { size: new_size, .. })
463 if size.checked_add(*new_size).is_some() =>
464 {
465 *last = Chunk::DontCare { start: *start, size: size + new_size };
466 return Ok(());
467 }
468 _ => {}
469 },
470 None => {}
471 }
472
473 r.push(chunk);
478 Ok(())
479}
480
481pub fn unsparse<W: Writer, R: Read + Seek>(
483 source: &mut R,
484 dest: &mut W,
485) -> Result<(), SparseError> {
486 let header: SparseHeader = deserialize_from(source)
487 .map_err(|e| SparseError::Deserialize { ty: SparseDataType::Header, source: e })?;
488 if !header.valid() {
489 return Err(SparseError::InvalidHeader);
490 }
491
492 for _ in 0..header.total_chunks {
493 expand_chunk(source, dest, header.blk_sz)?;
494 }
495 let offset = dest.stream_position()?;
497 dest.set_len(offset)?;
498 dest.flush()?;
499 Ok(())
500}
501
502fn expand_chunk<R: Read + Seek, W: Write + Seek>(
504 source: &mut R,
505 dest: &mut W,
506 block_size: u32,
507) -> Result<(), SparseError> {
508 let header: ChunkHeader = deserialize_from(source)
509 .map_err(|e| SparseError::Deserialize { ty: SparseDataType::ChunkHeader, source: e })?;
510 if !header.valid() {
511 return Err(SparseError::InvalidChunkHeader);
512 }
513 let size = (header.chunk_sz * block_size) as usize;
514 match header.chunk_type {
515 format::CHUNK_TYPE_RAW => {
516 let limit = source.stream_position()? as usize + size;
517 std::io::copy(&mut LimitedReader(source, limit), dest)
518 .map_err(SparseError::CopyContents)?;
519 }
520 format::CHUNK_TYPE_FILL => {
521 let value: [u8; 4] = deserialize_from(source).map_err(|e| {
522 SparseError::Deserialize { ty: SparseDataType::FillValue, source: e }
523 })?;
524 assert!(size % 4 == 0);
525 let repeated = value.repeat(size / 4);
526 dest.write_all(&repeated).map_err(SparseError::FillContents)?;
527 }
528 format::CHUNK_TYPE_DONT_CARE => {
529 dest.seek(SeekFrom::Current(size as i64)).map_err(SparseError::SkipContents)?;
530 }
531 format::CHUNK_TYPE_CRC32 => {
532 let _: u32 = deserialize_from(source).map_err(|e| SparseError::Deserialize {
533 ty: SparseDataType::Checksum,
534 source: e,
535 })?;
536 }
537 _ => return Err(SparseError::InvalidChunkType(header.chunk_type)),
538 };
539 Ok(())
540}
541
542fn resparse(
548 sparse_file: SparseFileWriter,
549 max_download_size: u64,
550) -> Result<Vec<SparseFileWriter>, SparseError> {
551 if max_download_size <= BLK_SIZE as u64 {
552 return Err(SparseError::MaxDownloadSizeTooSmall(max_download_size, BLK_SIZE));
553 }
554 let mut ret = Vec::<SparseFileWriter>::new();
555
556 let sunk_file_length = format::SPARSE_HEADER_SIZE as u64
559 + Chunk::DontCare { start: 0, size: BLK_SIZE.into() }.chunk_data_len() as u64
560 + Chunk::Crc32 { checksum: 2345 }.chunk_data_len() as u64;
561
562 let mut chunk_pos = 0;
563 let mut output_offset = 0;
564 while chunk_pos < sparse_file.chunks.len() {
565 log::trace!("Starting a new file at chunk position: {}", chunk_pos);
566
567 let mut file_len = 0;
568 file_len += sunk_file_length;
569
570 let mut chunks = Vec::<Chunk>::new();
571 if chunk_pos > 0 {
572 log::trace!("Adding a DontCare chunk offset: {}", chunk_pos);
575 let dont_care = Chunk::DontCare { start: 0, size: output_offset };
576 chunks.push(dont_care);
577 }
578
579 loop {
580 match sparse_file.chunks.get(chunk_pos) {
581 Some(chunk) => {
582 let curr_chunk_data_len = chunk.chunk_data_len() as u64;
583 if (file_len + curr_chunk_data_len) > max_download_size {
584 log::trace!(
585 "Current file size is: {} and adding another chunk of len: {} would \
586 put us over our max: {}",
587 file_len,
588 curr_chunk_data_len,
589 max_download_size
590 );
591
592 let remainder_size = sparse_file.total_bytes() - output_offset;
598 let dont_care =
599 Chunk::DontCare { start: output_offset, size: remainder_size };
600 chunks.push(dont_care);
601 break;
602 }
603 log::trace!(
604 "chunk: {} curr_chunk_data_len: {} current file size: {} \
605 max_download_size: {} diff: {}",
606 chunk_pos,
607 curr_chunk_data_len,
608 file_len,
609 max_download_size,
610 (max_download_size - file_len - curr_chunk_data_len)
611 );
612 add_sparse_chunk(&mut chunks, chunk.clone())?;
613 file_len += curr_chunk_data_len;
614 chunk_pos = chunk_pos + 1;
615 output_offset += chunk.output_size() as u64;
616 }
617 None => {
618 log::trace!("Finished iterating chunks");
619 break;
620 }
621 }
622 }
623 let resparsed = SparseFileWriter::new(chunks);
624 log::trace!("resparse: Adding new SparseFile: {}", resparsed);
625 ret.push(resparsed);
626 }
627
628 Ok(ret)
629}
630
631pub fn resparse_sparse_img<R: Read + std::io::Seek>(
641 reader: &mut SparseReader<R>,
642 dir: &Path,
643 max_download_size: u64,
644) -> Result<Vec<TempPath>, SparseError> {
645 log::debug!("Building writer from Reader");
646 let mut chunks = vec![];
647 let header_sunk = SPARSE_HEADER_SIZE as u64;
650 let mut raw_chunks_encountered = 0;
651 for (chunk, offset) in reader.chunks() {
652 log::info!("resparse_sparse_img. Processing chunk: {} with offset: {:#?}", chunk, offset);
653 if chunk.chunk_type() == format::CHUNK_TYPE_RAW {
654 raw_chunks_encountered += 1;
655 let blks = chunk.output_blocks(BLK_SIZE);
657 log::trace!("resparse_sparse_image: splitting RAW chunk into {} chunks", blks);
658 let sunk: u64 = header_sunk + (raw_chunks_encountered * CHUNK_HEADER_SIZE) as u64;
659 for i in 0..blks {
660 let start: u64 = offset.unwrap_or(0) + (BLK_SIZE * i) as u64 - sunk;
661 log::debug!("resparse_sparse_img: adding RAW chunk at start: {}", start);
662 chunks.push(Chunk::Raw { start, size: BLK_SIZE.into() })
663 }
664 } else {
665 chunks.push(chunk.clone());
666 }
667 }
668 let sparse_file = SparseFileWriter::new(chunks);
669
670 let mut ret = Vec::<TempPath>::new();
671 log::debug!("Resparsing sparse file");
672 for re_sparsed_file in resparse(sparse_file, max_download_size)? {
673 let (file, temp_path) = NamedTempFile::new_in(dir)?.into_parts();
674 let mut file_create = File::from(file);
675
676 log::debug!("Writing resparsed {} to disk", re_sparsed_file);
677 re_sparsed_file.write(reader, &mut file_create)?;
678
679 ret.push(temp_path);
680 }
681
682 log::debug!("Finished building sparse files");
683
684 Ok(ret)
685}
686
687pub fn build_sparse_files(
698 name: &str,
699 file_to_upload: &str,
700 dir: &Path,
701 max_download_size: u64,
702) -> Result<Vec<TempPath>, SparseError> {
703 if max_download_size <= BLK_SIZE as u64 {
704 return Err(SparseError::MaxDownloadSizeTooSmall(max_download_size, BLK_SIZE));
705 }
706 log::debug!("Building sparse files for: {}. File: {}", name, file_to_upload);
707 let mut in_file = File::open(file_to_upload)?;
708
709 let mut total_read: usize = 0;
710 let mut chunks =
712 Vec::<Chunk>::with_capacity((in_file.metadata()?.len() as usize / BLK_SIZE as usize) + 1);
713
714 let mut buf = [0u8; BLK_SIZE as usize];
715 loop {
716 let read = in_file.read(&mut buf)?;
717 if read == 0 {
718 break;
719 }
720
721 let is_fill = buf.chunks(4).collect::<Vec<&[u8]>>().array_windows().all(|[a, b]| a == b);
722
723 if is_fill {
724 let value: u32 =
730 bincode::deserialize(&buf[0..4]).map_err(|e| SparseError::Deserialize {
731 ty: SparseDataType::FillValue,
732 source: DeserializeError::Bincode(e),
733 })?;
734 let fill = Chunk::Fill {
736 start: total_read as u64,
737 size: buf.len().try_into().unwrap(),
738 value,
739 };
740 log::trace!("Sparsing file: {}. Created: {}", file_to_upload, fill);
741 chunks.push(fill);
742 } else {
743 let raw = Chunk::Raw { start: total_read as u64, size: buf.len().try_into().unwrap() };
745 log::trace!("Sparsing file: {}. Created: {}", file_to_upload, raw);
746 chunks.push(raw);
747 if read < buf.len() {
748 let skip_end =
752 Chunk::DontCare { start: (total_read + read) as u64, size: BLK_SIZE.into() };
753 chunks.push(skip_end);
754 }
755 }
756 total_read += read;
757 }
758
759 log::trace!("Creating sparse file from: {} chunks", chunks.len());
760
761 let sparse_file = SparseFileWriter::new(chunks);
771 log::trace!("Created sparse file: {}", sparse_file);
772
773 let mut ret = Vec::<TempPath>::new();
774 log::trace!("Resparsing sparse file");
775 for re_sparsed_file in resparse(sparse_file, max_download_size)? {
776 let (file, temp_path) = NamedTempFile::new_in(dir)?.into_parts();
777 let mut file_create = File::from(file);
778
779 log::trace!("Writing resparsed {} to disk", re_sparsed_file);
780 re_sparsed_file.write(&mut in_file, &mut file_create)?;
781
782 ret.push(temp_path);
783 }
784
785 log::debug!("Finished building sparse files");
786
787 Ok(ret)
788}
789
790#[cfg(test)]
794mod test {
795 #[cfg(target_os = "linux")]
796 use crate::build_sparse_files;
797
798 use super::builder::{DataSource, SparseImageBuilder};
799 use super::{
800 BLK_SIZE, Chunk, NO_SOURCE, SparseFileWriter, add_sparse_chunk, resparse, unsparse,
801 };
802 use rand::rngs::SmallRng;
803 use rand::{RngCore, SeedableRng};
804 use std::io::{Cursor, Read as _, Seek as _, SeekFrom, Write as _};
805 #[cfg(target_os = "linux")]
806 use std::path::Path;
807 #[cfg(target_os = "linux")]
808 use std::process::{Command, Stdio};
809 use tempfile::{NamedTempFile, TempDir};
810
811 #[test]
812 fn test_fill_into_bytes() {
813 let mut dest = Cursor::new(Vec::<u8>::new());
814
815 let fill_chunk = Chunk::Fill { start: 0, size: (5 * BLK_SIZE).into(), value: 365 };
816 fill_chunk.write(NO_SOURCE, &mut dest, BLK_SIZE).unwrap();
817 assert_eq!(dest.into_inner(), [194, 202, 0, 0, 5, 0, 0, 0, 16, 0, 0, 0, 109, 1, 0, 0]);
818 }
819
820 #[test]
821 fn test_raw_into_bytes() {
822 const EXPECTED_RAW_BYTES: [u8; 22] =
823 [193, 202, 0, 0, 1, 0, 0, 0, 12, 16, 0, 0, 49, 50, 51, 52, 53, 0, 0, 0, 0, 0];
824
825 let mut source = Cursor::new(Vec::<u8>::from(&b"12345"[..]));
826 let mut sparse = Cursor::new(Vec::<u8>::new());
827 let chunk = Chunk::Raw { start: 0, size: BLK_SIZE.into() };
828
829 chunk.write(Some(&mut source), &mut sparse, BLK_SIZE).unwrap();
830 let buf = sparse.into_inner();
831 assert_eq!(buf.len(), 4108);
832 assert_eq!(&buf[..EXPECTED_RAW_BYTES.len()], EXPECTED_RAW_BYTES);
833 assert_eq!(&buf[EXPECTED_RAW_BYTES.len()..], &[0u8; 4108 - EXPECTED_RAW_BYTES.len()]);
834 }
835
836 #[test]
837 fn test_dont_care_into_bytes() {
838 let mut dest = Cursor::new(Vec::<u8>::new());
839 let chunk = Chunk::DontCare { start: 0, size: (5 * BLK_SIZE).into() };
840
841 chunk.write(NO_SOURCE, &mut dest, BLK_SIZE).unwrap();
842 assert_eq!(dest.into_inner(), [195, 202, 0, 0, 5, 0, 0, 0, 12, 0, 0, 0]);
843 }
844
845 #[test]
846 fn test_sparse_file_into_bytes() {
847 let mut source = Cursor::new(Vec::<u8>::from(&b"123"[..]));
848 let mut sparse = Cursor::new(Vec::<u8>::new());
849 let mut chunks = Vec::<Chunk>::new();
850 let fill = Chunk::Fill { start: 0, size: 4096, value: 5 };
852 chunks.push(fill);
853 let raw = Chunk::Raw { start: 0, size: 12288 };
855 chunks.push(raw);
856 let dontcare = Chunk::DontCare { start: 0, size: 4096 };
858 chunks.push(dontcare);
859
860 let sparsefile = SparseFileWriter::new(chunks);
861 sparsefile.write(&mut source, &mut sparse).unwrap();
862
863 sparse.seek(SeekFrom::Start(0)).unwrap();
864 let mut unsparsed = Cursor::new(Vec::<u8>::new());
865 unsparse(&mut sparse, &mut unsparsed).unwrap();
866 let buf = unsparsed.into_inner();
867 assert_eq!(buf.len(), 4096 + 12288 + 4096);
868 {
869 let chunks = buf[..4096].chunks(4);
870 for chunk in chunks {
871 assert_eq!(chunk, &[5u8, 0, 0, 0]);
872 }
873 }
874 assert_eq!(&buf[4096..4099], b"123");
875 assert_eq!(&buf[4099..16384], &[0u8; 12285]);
876 assert_eq!(&buf[16384..], &[0u8; 4096]);
877 }
878
879 #[test]
883 fn test_resparse_bails_on_too_small_size() {
884 let sparse = SparseFileWriter::new(Vec::<Chunk>::new());
885 assert!(resparse(sparse, 4095).is_err());
886 }
887
888 #[test]
889 fn test_resparse_splits() {
890 let max_download_size = 4096 * 2;
891
892 let mut chunks = Vec::<Chunk>::new();
893 chunks.push(Chunk::Raw { start: 0, size: 4096 });
894 chunks.push(Chunk::Fill { start: 4096, size: 4096, value: 2 });
895 chunks.push(Chunk::Raw { start: 8192, size: 4096 });
898
899 let input_sparse_file = SparseFileWriter::new(chunks);
900 let resparsed_files = resparse(input_sparse_file, max_download_size).unwrap();
901 assert_eq!(2, resparsed_files.len());
902
903 assert_eq!(3, resparsed_files[0].chunks.len());
904 assert_eq!(Chunk::Raw { start: 0, size: 4096 }, resparsed_files[0].chunks[0]);
905 assert_eq!(Chunk::Fill { start: 4096, size: 4096, value: 2 }, resparsed_files[0].chunks[1]);
906 assert_eq!(Chunk::DontCare { start: 8192, size: 4096 }, resparsed_files[0].chunks[2]);
907
908 assert_eq!(2, resparsed_files[1].chunks.len());
909 assert_eq!(Chunk::DontCare { start: 0, size: 8192 }, resparsed_files[1].chunks[0]);
910 assert_eq!(Chunk::Raw { start: 8192, size: 4096 }, resparsed_files[1].chunks[1]);
911 }
912
913 #[test]
917 fn test_add_sparse_chunk_adds_empty() {
918 let init_vec = Vec::<Chunk>::new();
919 let mut res = init_vec.clone();
920 add_sparse_chunk(&mut res, Chunk::Fill { start: 0, size: 4096, value: 1 }).unwrap();
921 assert_eq!(0, init_vec.len());
922 assert_ne!(init_vec, res);
923 assert_eq!(Chunk::Fill { start: 0, size: 4096, value: 1 }, res[0]);
924 }
925
926 #[test]
927 fn test_add_sparse_chunk_fill() {
928 {
930 let mut init_vec = Vec::<Chunk>::new();
931 init_vec.push(Chunk::Fill { start: 0, size: 8192, value: 1 });
932 let mut res = init_vec.clone();
933 add_sparse_chunk(&mut res, Chunk::Fill { start: 0, size: 8192, value: 1 }).unwrap();
934 assert_eq!(1, res.len());
935 assert_eq!(Chunk::Fill { start: 0, size: 16384, value: 1 }, res[0]);
936 }
937
938 {
940 let mut init_vec = Vec::<Chunk>::new();
941 init_vec.push(Chunk::Fill { start: 0, size: 4096, value: 1 });
942 let mut res = init_vec.clone();
943 add_sparse_chunk(&mut res, Chunk::Fill { start: 0, size: 4096, value: 2 }).unwrap();
944 assert_ne!(res, init_vec);
945 assert_eq!(2, res.len());
946 assert_eq!(
947 res,
948 [
949 Chunk::Fill { start: 0, size: 4096, value: 1 },
950 Chunk::Fill { start: 0, size: 4096, value: 2 }
951 ]
952 );
953 }
954
955 {
957 let mut init_vec = Vec::<Chunk>::new();
958 init_vec.push(Chunk::Fill { start: 0, size: 4096, value: 2 });
959 let mut res = init_vec.clone();
960 add_sparse_chunk(&mut res, Chunk::DontCare { start: 0, size: 4096 }).unwrap();
961 assert_ne!(res, init_vec);
962 assert_eq!(2, res.len());
963 assert_eq!(
964 res,
965 [
966 Chunk::Fill { start: 0, size: 4096, value: 2 },
967 Chunk::DontCare { start: 0, size: 4096 }
968 ]
969 );
970 }
971
972 {
974 let mut init_vec = Vec::<Chunk>::new();
975 init_vec.push(Chunk::Fill { start: 0, size: 4096, value: 1 });
976 let mut res = init_vec.clone();
977 add_sparse_chunk(&mut res, Chunk::Fill { start: 0, size: u64::MAX - 4095, value: 1 })
978 .unwrap();
979 assert_ne!(res, init_vec);
980 assert_eq!(2, res.len());
981 assert_eq!(
982 res,
983 [
984 Chunk::Fill { start: 0, size: 4096, value: 1 },
985 Chunk::Fill { start: 0, size: u64::MAX - 4095, value: 1 }
986 ]
987 );
988 }
989 }
990
991 #[test]
992 fn test_add_sparse_chunk_dont_care() {
993 {
995 let mut init_vec = Vec::<Chunk>::new();
996 init_vec.push(Chunk::DontCare { start: 0, size: 4096 });
997 let mut res = init_vec.clone();
998 add_sparse_chunk(&mut res, Chunk::DontCare { start: 0, size: 4096 }).unwrap();
999 assert_eq!(1, res.len());
1000 assert_eq!(Chunk::DontCare { start: 0, size: 8192 }, res[0]);
1001 }
1002
1003 {
1005 let mut init_vec = Vec::<Chunk>::new();
1006 init_vec.push(Chunk::DontCare { start: 0, size: 4096 });
1007 let mut res = init_vec.clone();
1008 add_sparse_chunk(&mut res, Chunk::Fill { start: 0, size: 4096, value: 1 }).unwrap();
1009 assert_eq!(2, res.len());
1010 assert_eq!(
1011 res,
1012 [
1013 Chunk::DontCare { start: 0, size: 4096 },
1014 Chunk::Fill { start: 0, size: 4096, value: 1 }
1015 ]
1016 );
1017 }
1018
1019 {
1021 let mut init_vec = Vec::<Chunk>::new();
1022 init_vec.push(Chunk::DontCare { start: 0, size: 4096 });
1023 let mut res = init_vec.clone();
1024 add_sparse_chunk(&mut res, Chunk::DontCare { start: 0, size: u64::MAX - 4095 })
1025 .unwrap();
1026 assert_eq!(2, res.len());
1027 assert_eq!(
1028 res,
1029 [
1030 Chunk::DontCare { start: 0, size: 4096 },
1031 Chunk::DontCare { start: 0, size: u64::MAX - 4095 }
1032 ]
1033 );
1034 }
1035 }
1036
1037 #[test]
1038 fn test_add_sparse_chunk_raw() {
1039 {
1041 let mut init_vec = Vec::<Chunk>::new();
1042 init_vec.push(Chunk::Raw { start: 0, size: 12288 });
1043 let mut res = init_vec.clone();
1044 add_sparse_chunk(&mut res, Chunk::Raw { start: 0, size: 16384 }).unwrap();
1045 assert_eq!(1, res.len());
1046 assert_eq!(Chunk::Raw { start: 0, size: 28672 }, res[0]);
1047 }
1048
1049 {
1051 let mut init_vec = Vec::<Chunk>::new();
1052 init_vec.push(Chunk::Raw { start: 0, size: 12288 });
1053 let mut res = init_vec.clone();
1054 add_sparse_chunk(&mut res, Chunk::Fill { start: 3, size: 8192, value: 1 }).unwrap();
1055 assert_eq!(2, res.len());
1056 assert_eq!(
1057 res,
1058 [
1059 Chunk::Raw { start: 0, size: 12288 },
1060 Chunk::Fill { start: 3, size: 8192, value: 1 }
1061 ]
1062 );
1063 }
1064
1065 {
1067 let mut init_vec = Vec::<Chunk>::new();
1068 init_vec.push(Chunk::Raw { start: 0, size: 4096 });
1069 let mut res = init_vec.clone();
1070 add_sparse_chunk(&mut res, Chunk::Raw { start: 0, size: u64::MAX - 4095 }).unwrap();
1071 assert_eq!(2, res.len());
1072 assert_eq!(
1073 res,
1074 [
1075 Chunk::Raw { start: 0, size: 4096 },
1076 Chunk::Raw { start: 0, size: u64::MAX - 4095 }
1077 ]
1078 );
1079 }
1080 }
1081
1082 #[test]
1083 fn test_add_sparse_chunk_crc32() {
1084 {
1086 let mut init_vec = Vec::<Chunk>::new();
1087 init_vec.push(Chunk::Crc32 { checksum: 1234 });
1088 let mut res = init_vec.clone();
1089 add_sparse_chunk(&mut res, Chunk::Crc32 { checksum: 2345 }).unwrap();
1090 assert_eq!(2, res.len());
1091 assert_eq!(res, [Chunk::Crc32 { checksum: 1234 }, Chunk::Crc32 { checksum: 2345 }]);
1092 }
1093
1094 {
1096 let mut init_vec = Vec::<Chunk>::new();
1097 init_vec.push(Chunk::Crc32 { checksum: 1234 });
1098 let mut res = init_vec.clone();
1099 add_sparse_chunk(&mut res, Chunk::Fill { start: 0, size: 4096, value: 1 }).unwrap();
1100 assert_eq!(2, res.len());
1101 assert_eq!(
1102 res,
1103 [Chunk::Crc32 { checksum: 1234 }, Chunk::Fill { start: 0, size: 4096, value: 1 }]
1104 );
1105 }
1106 }
1107
1108 #[test]
1113 fn test_roundtrip() {
1114 let tmpdir = TempDir::new().unwrap();
1115
1116 let (mut file, _temp_path) = NamedTempFile::new_in(&tmpdir).unwrap().into_parts();
1118 let mut rng = SmallRng::from_os_rng();
1119 let mut buf = Vec::<u8>::new();
1120 buf.resize(1 * 4096, 0);
1121 rng.fill_bytes(&mut buf);
1122 file.write_all(&buf).unwrap();
1123 file.flush().unwrap();
1124 file.seek(SeekFrom::Start(0)).unwrap();
1125 let content_size = buf.len();
1126
1127 let mut sparse_file = NamedTempFile::new_in(&tmpdir).unwrap().into_file();
1129 SparseImageBuilder::new()
1130 .add_source(DataSource::Buffer(Box::new([0xffu8; 8192])))
1131 .add_source(DataSource::Reader { reader: Box::new(file), size: content_size as u64 })
1132 .add_source(DataSource::Fill(0xaaaa_aaaau32, 1024))
1133 .add_source(DataSource::Skip(16384))
1134 .build(&mut sparse_file)
1135 .expect("Build sparse image failed");
1136 sparse_file.seek(SeekFrom::Start(0)).unwrap();
1137
1138 let mut orig_file = NamedTempFile::new_in(&tmpdir).unwrap().into_file();
1139 unsparse(&mut sparse_file, &mut orig_file).expect("unsparse failed");
1140 orig_file.seek(SeekFrom::Start(0)).unwrap();
1141
1142 let mut unsparsed_bytes = vec![];
1143 orig_file.read_to_end(&mut unsparsed_bytes).expect("Failed to read unsparsed image");
1144 assert_eq!(unsparsed_bytes.len(), 8192 + 20480 + content_size);
1145 assert_eq!(&unsparsed_bytes[..8192], &[0xffu8; 8192]);
1146 assert_eq!(&unsparsed_bytes[8192..8192 + content_size], &buf[..]);
1147 assert_eq!(&unsparsed_bytes[8192 + content_size..12288 + content_size], &[0xaau8; 4096]);
1148 assert_eq!(&unsparsed_bytes[12288 + content_size..], &[0u8; 16384]);
1149 }
1150
1151 #[test]
1152 #[cfg(target_os = "linux")]
1164 fn test_with_simg2img() {
1165 let simg2img_path = Path::new("./host_x64/test_data/storage/sparse/simg2img");
1166 assert!(
1167 Path::exists(simg2img_path),
1168 "simg2img binary must exist at {}",
1169 simg2img_path.display()
1170 );
1171
1172 let tmpdir = TempDir::new().unwrap();
1173
1174 let (mut file, temp_path) = NamedTempFile::new_in(&tmpdir).unwrap().into_parts();
1176 let mut rng = SmallRng::from_os_rng();
1177 let mut buf = Vec::<u8>::new();
1178 buf.resize(50 * 4096 + 1244, 0);
1180 rng.fill_bytes(&mut buf);
1181 file.write_all(&buf).unwrap();
1182 file.flush().unwrap();
1183 file.seek(SeekFrom::Start(0)).unwrap();
1184
1185 let files = build_sparse_files(
1187 "test",
1188 temp_path.to_path_buf().to_str().expect("Should succeed"),
1189 tmpdir.path(),
1190 4096 * 2,
1191 )
1192 .unwrap();
1193
1194 let mut simg2img_output = tmpdir.path().to_path_buf();
1195 simg2img_output.push("output");
1196
1197 let mut simg2img = Command::new(simg2img_path)
1198 .args(&files[..])
1199 .arg(&simg2img_output)
1200 .stdout(Stdio::piped())
1201 .stderr(Stdio::piped())
1202 .spawn()
1203 .expect("Failed to spawn simg2img");
1204 let res = simg2img.wait().expect("simg2img did was not running");
1205 assert!(res.success(), "simg2img did not succeed");
1206 let mut simg2img_stdout = simg2img.stdout.take().expect("Get stdout from simg2img");
1207 let mut simg2img_stderr = simg2img.stderr.take().expect("Get stderr from simg2img");
1208
1209 let mut stdout = String::new();
1210 simg2img_stdout.read_to_string(&mut stdout).expect("Reading simg2img stdout");
1211 assert_eq!(stdout, "");
1212
1213 let mut stderr = String::new();
1214 simg2img_stderr.read_to_string(&mut stderr).expect("Reading simg2img stderr");
1215 assert_eq!(stderr, "");
1216
1217 let simg2img_output_bytes =
1218 std::fs::read(simg2img_output).expect("Failed to read simg2img output");
1219
1220 assert_eq!(
1221 buf,
1222 simg2img_output_bytes[0..buf.len()],
1223 "Output from simg2img should match our generated file"
1224 );
1225
1226 assert_eq!(
1227 simg2img_output_bytes[buf.len()..],
1228 vec![0u8; simg2img_output_bytes.len() - buf.len()],
1229 "The remainder of our simg2img_output_bytes should be 0"
1230 );
1231 }
1232
1233 #[test]
1234 #[cfg(target_os = "linux")]
1235 fn test_resparse_from_sparse() {
1236 use crate::reader::SparseReader;
1237 use crate::resparse_sparse_img;
1238
1239 let simg2img_path = Path::new("./host_x64/test_data/storage/sparse/simg2img");
1240 assert!(
1241 Path::exists(simg2img_path),
1242 "simg2img binary must exist at {}",
1243 simg2img_path.display()
1244 );
1245
1246 let tmpdir = TempDir::new().unwrap();
1247
1248 let (mut file, _temp_path) = NamedTempFile::new_in(&tmpdir).unwrap().into_parts();
1250 let mut rng = SmallRng::from_os_rng();
1251 let mut buf = Vec::<u8>::new();
1252 buf.resize(10 * 4096, 0);
1253 rng.fill_bytes(&mut buf);
1254 file.write_all(&buf).unwrap();
1255 file.flush().unwrap();
1256 file.seek(SeekFrom::Start(0)).unwrap();
1257 let content_size = buf.len();
1258
1259 let sparse_file_tmp = NamedTempFile::new_in(&tmpdir).unwrap();
1261 let mut sparse_file = sparse_file_tmp.into_file();
1262 SparseImageBuilder::new()
1263 .add_source(DataSource::Buffer(Box::new([0xffu8; 4096 * 2])))
1264 .add_source(DataSource::Reader { reader: Box::new(file), size: content_size as u64 })
1265 .add_source(DataSource::Fill(0xaaaa_aaaau32, 1024))
1266 .add_source(DataSource::Skip(16384))
1267 .build(&mut sparse_file)
1268 .expect("Build sparse image failed");
1269 sparse_file.seek(SeekFrom::Start(0)).unwrap();
1270
1271 let mut reader = SparseReader::new(sparse_file).expect("create reader");
1272
1273 let files = resparse_sparse_img(&mut reader, tmpdir.path(), 4096 * 3).unwrap();
1274
1275 let mut simg2img_output_sparsed = tmpdir.path().to_path_buf();
1277 simg2img_output_sparsed.push("output_sparsed");
1278
1279 let mut simg2img_sparsed = Command::new(simg2img_path)
1280 .args(&files[..])
1281 .arg(&simg2img_output_sparsed)
1282 .stdout(Stdio::piped())
1283 .stderr(Stdio::piped())
1284 .spawn()
1285 .expect("Failed to spawn simg2img");
1286 let res = simg2img_sparsed.wait().expect("simg2img did was not running");
1287 assert!(res.success(), "simg2img did not succeed");
1288 let mut simg2img_stdout = simg2img_sparsed.stdout.take().expect("Get stdout from simg2img");
1289 let mut simg2img_stderr = simg2img_sparsed.stderr.take().expect("Get stderr from simg2img");
1290
1291 let mut stdout = String::new();
1292 simg2img_stdout.read_to_string(&mut stdout).expect("Reading simg2img stdout");
1293 assert_eq!(stdout, "");
1294
1295 let mut stderr = String::new();
1296 simg2img_stderr.read_to_string(&mut stderr).expect("Reading simg2img stderr");
1297 assert_eq!(stderr, "");
1298 }
1299}