1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
//! Sparse file data structures.

use std::cell::RefCell;
use std::fs::File as StdFile;
use std::rc::Rc;
use std::slice::Iter;

use constants::{BLOCK_SIZE, CHUNK_HEADER_SIZE};
use convert::TryInto;

/// An iterator over sparse file chunks.
pub type ChunkIter<'a> = Iter<'a, Chunk>;

/// A sparse file.
///
/// Provides methods to add chunks and iterate over its chunks.
#[derive(Debug, Default)]
pub struct File {
    chunks: Vec<Chunk>,
}

impl File {
    /// Creates a new empty sparse file.
    pub fn new() -> Self {
        Self { chunks: Vec::new() }
    }

    /// Returns the number of blocks in the sparse file.
    pub fn num_blocks(&self) -> u32 {
        self.chunks
            .iter()
            .fold(0, |sum, chunk| sum + chunk.num_blocks())
    }

    /// Returns the number of chunks in the sparse file.
    pub fn num_chunks(&self) -> u32 {
        self.chunks
            .len()
            .try_into()
            .expect("number of chunks doesn't fit into u32")
    }

    /// Adds a chunk to the sparse file.
    pub fn add_chunk(&mut self, chunk: Chunk) {
        self.chunks.push(chunk);
    }

    /// Iterates over the chunks in the sparse file.
    pub fn chunk_iter(&self) -> ChunkIter {
        self.chunks.iter()
    }
}

/// A sparse file chunk.
///
/// A chunk represents either:
/// - a part of the raw image of size `num_blocks * BLOCK_SIZE`, or
/// - the CRC32 checksum of the raw image data up to this point
#[derive(Debug)]
pub enum Chunk {
    /// Chunk representing blocks of raw bytes.
    ///
    /// To keep memory usage low, a raw chunk holds a reference to its backing
    /// file instead of the actual raw data.
    Raw {
        /// Reference to the backing file.
        file: Rc<RefCell<StdFile>>,
        /// Offset into the backing file to the start of the raw bytes.
        offset: u64,
        /// Number of blocks contained in this chunk.
        num_blocks: u32,
    },

    /// Chunk representing blocks filled with the same 4-byte value.
    Fill {
        /// The 4-byte fill value.
        fill: [u8; 4],
        /// Number of blocks contained in this chunk.
        num_blocks: u32,
    },

    /// Chunk representing blocks of ignored data.
    DontCare {
        /// Number of blocks contained in this chunk.
        num_blocks: u32,
    },

    /// Chunk holding the CRC32 checksum value of all previous data chunks.
    Crc32 {
        /// The CRC32 checksum value.
        crc: u32,
    },
}

impl Chunk {
    /// Returns this chunk's size in a sparse image.
    pub fn sparse_size(&self) -> u32 {
        let body_size = match self {
            Chunk::Raw { num_blocks, .. } => num_blocks * BLOCK_SIZE,
            Chunk::Fill { .. } | Chunk::Crc32 { .. } => 4,
            Chunk::DontCare { .. } => 0,
        };
        u32::from(CHUNK_HEADER_SIZE) + body_size
    }

    /// Returns the size of this chunk's decoded raw data.
    pub fn raw_size(&self) -> u32 {
        self.num_blocks() * BLOCK_SIZE
    }

    /// Returns the number of blocks contained in this chunk.
    pub fn num_blocks(&self) -> u32 {
        match *self {
            Chunk::Raw { num_blocks, .. }
            | Chunk::Fill { num_blocks, .. }
            | Chunk::DontCare { num_blocks } => num_blocks,
            Chunk::Crc32 { .. } => 0,
        }
    }
}