Skip to main content

disk_core/unix_common/
compact.rs

1//! Generic compact reader framework for Unix-like filesystems.
2//!
3//! Provides a streaming `Read` implementation over a virtual compacted image
4//! described by a sequence of [`CompactSection`]s. Each filesystem's compact
5//! reader builds a [`CompactLayout`] describing pre-built metadata regions,
6//! mapped data blocks, and zero-fill gaps, then wraps it in a
7//! [`CompactStreamReader`] that handles all the streaming I/O.
8
9use std::io::{self, Read, Seek, SeekFrom};
10
11/// A section in the virtual compacted image.
12pub enum CompactSection {
13    /// Pre-built data served from memory (boot sectors, rebuilt metadata, etc.)
14    PreBuilt(Vec<u8>),
15
16    /// A contiguous run of mapped blocks from the source.
17    ///
18    /// Each entry in `old_blocks` is the source block number. The blocks are
19    /// emitted sequentially in the virtual image — `old_blocks[0]` comes first,
20    /// then `old_blocks[1]`, etc. The source byte offset for block `b` is:
21    /// `source_partition_offset + source_data_start + b * block_size`.
22    MappedBlocks {
23        /// For each new block index in this section, the old block number in the source.
24        old_blocks: Vec<u64>,
25    },
26
27    /// Zero-filled region of the given byte length.
28    Zeros(u64),
29}
30
31impl CompactSection {
32    /// Returns the byte size of this section.
33    fn size(&self, block_size: usize) -> u64 {
34        match self {
35            CompactSection::PreBuilt(data) => data.len() as u64,
36            CompactSection::MappedBlocks { old_blocks } => {
37                old_blocks.len() as u64 * block_size as u64
38            }
39            CompactSection::Zeros(len) => *len,
40        }
41    }
42}
43
44/// Describes the layout of a virtual compacted image.
45pub struct CompactLayout {
46    /// Ordered sequence of sections that make up the virtual image.
47    pub sections: Vec<CompactSection>,
48    /// Block size in bytes (e.g. 1024, 2048, or 4096 for ext2/3/4).
49    pub block_size: usize,
50    /// Byte offset of the data block region relative to the partition start.
51    ///
52    /// For ext this would typically be 0 since block numbers are absolute
53    /// within the partition. For FAT this would be the offset past reserved
54    /// sectors and FAT tables. Set to 0 if block numbers are partition-relative.
55    pub source_data_start: u64,
56    /// Absolute byte offset of the partition within the source device.
57    pub source_partition_offset: u64,
58}
59
60impl CompactLayout {
61    /// Total byte size of the virtual compacted image.
62    pub fn total_size(&self) -> u64 {
63        self.sections.iter().map(|s| s.size(self.block_size)).sum()
64    }
65}
66
67/// Cached information about each section's position in the virtual image.
68struct SectionSpan {
69    /// Starting virtual byte offset of this section.
70    start: u64,
71    /// Ending virtual byte offset (exclusive).
72    end: u64,
73}
74
75/// Generic streaming reader over a [`CompactLayout`].
76///
77/// Implements `Read` by serving bytes from pre-built buffers, mapped source
78/// blocks, or zero-fill regions as described by the layout. Reads that span
79/// section or block boundaries are handled transparently.
80pub struct CompactStreamReader<R: Read + Seek> {
81    source: R,
82    layout: CompactLayout,
83    spans: Vec<SectionSpan>,
84    position: u64,
85    total_size: u64,
86    block_buf: Vec<u8>,
87    /// Which source block is currently in `block_buf`, or `None`.
88    cached_block: Option<u64>,
89}
90
91impl<R: Read + Seek> CompactStreamReader<R> {
92    /// Create a new compact stream reader.
93    pub fn new(source: R, layout: CompactLayout) -> Self {
94        let total_size = layout.total_size();
95        let block_size = layout.block_size;
96
97        // Pre-compute section spans for fast lookup
98        let mut spans = Vec::with_capacity(layout.sections.len());
99        let mut offset = 0u64;
100        for section in &layout.sections {
101            let size = section.size(block_size);
102            spans.push(SectionSpan {
103                start: offset,
104                end: offset + size,
105            });
106            offset += size;
107        }
108
109        Self {
110            source,
111            layout,
112            spans,
113            position: 0,
114            total_size,
115            block_buf: vec![0u8; block_size],
116            cached_block: None,
117        }
118    }
119
120    /// Returns the total size of the virtual compacted image.
121    pub fn total_size(&self) -> u64 {
122        self.total_size
123    }
124
125    /// Read a source block into `self.block_buf`, using the cache.
126    fn load_block(&mut self, block_num: u64) -> io::Result<()> {
127        if self.cached_block == Some(block_num) {
128            return Ok(());
129        }
130        let offset = self.layout.source_partition_offset
131            + self.layout.source_data_start
132            + block_num * self.layout.block_size as u64;
133        self.source.seek(SeekFrom::Start(offset))?;
134        self.source.read_exact(&mut self.block_buf)?;
135        self.cached_block = Some(block_num);
136        Ok(())
137    }
138
139    /// Find which section contains the given virtual position.
140    /// Returns `(section_index, offset_within_section)`.
141    fn find_section(&self, pos: u64) -> Option<(usize, u64)> {
142        // Linear scan is fine for typical layouts with < 10 sections.
143        // Could switch to binary search if layouts grow large.
144        for (i, span) in self.spans.iter().enumerate() {
145            if pos < span.end {
146                return Some((i, pos - span.start));
147            }
148        }
149        None
150    }
151}
152
153impl<R: Read + Seek> Read for CompactStreamReader<R> {
154    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
155        if self.position >= self.total_size {
156            return Ok(0);
157        }
158
159        let to_read = buf.len().min((self.total_size - self.position) as usize);
160        if to_read == 0 {
161            return Ok(0);
162        }
163
164        let mut filled = 0;
165
166        while filled < to_read {
167            let current_pos = self.position + filled as u64;
168            if current_pos >= self.total_size {
169                break;
170            }
171
172            let (section_idx, offset_in_section) = match self.find_section(current_pos) {
173                Some(v) => v,
174                None => break,
175            };
176
177            let section = &self.layout.sections[section_idx];
178            let section_size = section.size(self.layout.block_size);
179            let remaining_in_section = (section_size - offset_in_section) as usize;
180
181            match section {
182                CompactSection::PreBuilt(data) => {
183                    let off = offset_in_section as usize;
184                    let copy_len = (to_read - filled).min(remaining_in_section);
185                    buf[filled..filled + copy_len].copy_from_slice(&data[off..off + copy_len]);
186                    filled += copy_len;
187                }
188
189                CompactSection::MappedBlocks { old_blocks } => {
190                    let block_size = self.layout.block_size;
191                    let block_idx = offset_in_section as usize / block_size;
192                    let offset_in_block = offset_in_section as usize % block_size;
193
194                    if block_idx >= old_blocks.len() {
195                        // Safety: emit zeros for out-of-range blocks
196                        let copy_len = (to_read - filled).min(remaining_in_section);
197                        buf[filled..filled + copy_len].fill(0);
198                        filled += copy_len;
199                        continue;
200                    }
201
202                    let source_block = old_blocks[block_idx];
203                    self.load_block(source_block)?;
204
205                    let remaining_in_block = block_size - offset_in_block;
206                    let copy_len = (to_read - filled).min(remaining_in_block);
207                    buf[filled..filled + copy_len].copy_from_slice(
208                        &self.block_buf[offset_in_block..offset_in_block + copy_len],
209                    );
210                    filled += copy_len;
211                }
212
213                CompactSection::Zeros(_) => {
214                    let copy_len = (to_read - filled).min(remaining_in_section);
215                    buf[filled..filled + copy_len].fill(0);
216                    filled += copy_len;
217                }
218            }
219        }
220
221        self.position += filled as u64;
222        Ok(filled)
223    }
224}
225
226#[cfg(test)]
227mod tests {
228    use super::*;
229    use std::io::Cursor;
230
231    #[test]
232    fn test_prebuilt_section() {
233        let layout = CompactLayout {
234            sections: vec![CompactSection::PreBuilt(vec![0xAA; 512])],
235            block_size: 512,
236            source_data_start: 0,
237            source_partition_offset: 0,
238        };
239        let source = Cursor::new(vec![0u8; 0]);
240        let mut reader = CompactStreamReader::new(source, layout);
241        assert_eq!(reader.total_size(), 512);
242        let mut buf = vec![0u8; 512];
243        reader.read_exact(&mut buf).unwrap();
244        assert!(buf.iter().all(|&b| b == 0xAA));
245    }
246
247    #[test]
248    fn test_zeros_section() {
249        let layout = CompactLayout {
250            sections: vec![CompactSection::Zeros(1024)],
251            block_size: 512,
252            source_data_start: 0,
253            source_partition_offset: 0,
254        };
255        let source = Cursor::new(vec![0u8; 0]);
256        let mut reader = CompactStreamReader::new(source, layout);
257        assert_eq!(reader.total_size(), 1024);
258        let mut buf = vec![0xFFu8; 1024];
259        reader.read_exact(&mut buf).unwrap();
260        assert!(buf.iter().all(|&b| b == 0x00));
261    }
262
263    #[test]
264    fn test_mapped_blocks() {
265        // Source has 4 blocks of 512 bytes each. Map blocks [3, 1] (reverse order).
266        let mut source_data = vec![0u8; 2048];
267        source_data[512..1024].fill(0xBB); // block 1
268        source_data[1536..2048].fill(0xDD); // block 3
269        let layout = CompactLayout {
270            sections: vec![CompactSection::MappedBlocks {
271                old_blocks: vec![3, 1],
272            }],
273            block_size: 512,
274            source_data_start: 0,
275            source_partition_offset: 0,
276        };
277        let mut reader = CompactStreamReader::new(Cursor::new(source_data), layout);
278        assert_eq!(reader.total_size(), 1024);
279        let mut buf = vec![0u8; 1024];
280        reader.read_exact(&mut buf).unwrap();
281        assert!(buf[..512].iter().all(|&b| b == 0xDD)); // block 3 first
282        assert!(buf[512..].iter().all(|&b| b == 0xBB)); // block 1 second
283    }
284
285    #[test]
286    fn test_cross_section_read() {
287        // PreBuilt(4 bytes) + Zeros(4 bytes), read 8 bytes at once
288        let layout = CompactLayout {
289            sections: vec![
290                CompactSection::PreBuilt(vec![0xFF; 4]),
291                CompactSection::Zeros(4),
292            ],
293            block_size: 512,
294            source_data_start: 0,
295            source_partition_offset: 0,
296        };
297        let mut reader = CompactStreamReader::new(Cursor::new(vec![]), layout);
298        assert_eq!(reader.total_size(), 8);
299        let mut buf = vec![0u8; 8];
300        reader.read_exact(&mut buf).unwrap();
301        assert_eq!(&buf, &[0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00]);
302    }
303
304    #[test]
305    fn test_multi_section_layout() {
306        // PreBuilt(512) + MappedBlocks([0]) + Zeros(512)
307        let mut source_data = vec![0u8; 512];
308        source_data.fill(0xCC);
309        let layout = CompactLayout {
310            sections: vec![
311                CompactSection::PreBuilt(vec![0xAA; 512]),
312                CompactSection::MappedBlocks {
313                    old_blocks: vec![0],
314                },
315                CompactSection::Zeros(512),
316            ],
317            block_size: 512,
318            source_data_start: 0,
319            source_partition_offset: 0,
320        };
321        let mut reader = CompactStreamReader::new(Cursor::new(source_data), layout);
322        assert_eq!(reader.total_size(), 1536);
323
324        let mut buf = vec![0u8; 1536];
325        reader.read_exact(&mut buf).unwrap();
326        assert!(buf[..512].iter().all(|&b| b == 0xAA)); // PreBuilt
327        assert!(buf[512..1024].iter().all(|&b| b == 0xCC)); // MappedBlocks
328        assert!(buf[1024..].iter().all(|&b| b == 0x00)); // Zeros
329    }
330
331    #[test]
332    fn test_partial_read() {
333        // Read less than a full section at a time
334        let layout = CompactLayout {
335            sections: vec![CompactSection::PreBuilt(vec![0xAA; 1024])],
336            block_size: 512,
337            source_data_start: 0,
338            source_partition_offset: 0,
339        };
340        let mut reader = CompactStreamReader::new(Cursor::new(vec![]), layout);
341
342        // Read 100 bytes at a time
343        let mut all = Vec::new();
344        let mut buf = [0u8; 100];
345        loop {
346            let n = reader.read(&mut buf).unwrap();
347            if n == 0 {
348                break;
349            }
350            all.extend_from_slice(&buf[..n]);
351        }
352        assert_eq!(all.len(), 1024);
353        assert!(all.iter().all(|&b| b == 0xAA));
354    }
355
356    #[test]
357    fn test_read_past_end() {
358        let layout = CompactLayout {
359            sections: vec![CompactSection::PreBuilt(vec![0xAA; 10])],
360            block_size: 512,
361            source_data_start: 0,
362            source_partition_offset: 0,
363        };
364        let mut reader = CompactStreamReader::new(Cursor::new(vec![]), layout);
365        let mut buf = vec![0u8; 20];
366        // read_exact should fail since only 10 bytes available
367        assert!(reader.read_exact(&mut buf).is_err());
368    }
369
370    #[test]
371    fn test_empty_layout() {
372        let layout = CompactLayout {
373            sections: vec![],
374            block_size: 512,
375            source_data_start: 0,
376            source_partition_offset: 0,
377        };
378        let mut reader = CompactStreamReader::new(Cursor::new(vec![]), layout);
379        assert_eq!(reader.total_size(), 0);
380        let mut buf = [0u8; 1];
381        assert_eq!(reader.read(&mut buf).unwrap(), 0);
382    }
383
384    #[test]
385    fn test_source_data_start_offset() {
386        // Source data starts at byte 1024 within the partition.
387        // Block 0 is at partition_offset + 1024 + 0*512 = 1024.
388        let mut source_data = vec![0u8; 2048];
389        source_data[1024..1536].fill(0xEE); // block 0 at source offset 1024
390        let layout = CompactLayout {
391            sections: vec![CompactSection::MappedBlocks {
392                old_blocks: vec![0],
393            }],
394            block_size: 512,
395            source_data_start: 1024,
396            source_partition_offset: 0,
397        };
398        let mut reader = CompactStreamReader::new(Cursor::new(source_data), layout);
399        let mut buf = vec![0u8; 512];
400        reader.read_exact(&mut buf).unwrap();
401        assert!(buf.iter().all(|&b| b == 0xEE));
402    }
403
404    #[test]
405    fn test_partition_offset() {
406        // Partition starts at byte 512 in the device.
407        // Block 0 at 512 + 0 + 0*512 = 512.
408        let mut source_data = vec![0u8; 1024];
409        source_data[512..1024].fill(0xDD);
410        let layout = CompactLayout {
411            sections: vec![CompactSection::MappedBlocks {
412                old_blocks: vec![0],
413            }],
414            block_size: 512,
415            source_data_start: 0,
416            source_partition_offset: 512,
417        };
418        let mut reader = CompactStreamReader::new(Cursor::new(source_data), layout);
419        let mut buf = vec![0u8; 512];
420        reader.read_exact(&mut buf).unwrap();
421        assert!(buf.iter().all(|&b| b == 0xDD));
422    }
423
424    #[test]
425    fn test_cross_block_read_in_mapped_section() {
426        // Two mapped blocks, read across the block boundary
427        let mut source_data = vec![0u8; 1024];
428        source_data[..512].fill(0xAA); // block 0
429        source_data[512..1024].fill(0xBB); // block 1
430        let layout = CompactLayout {
431            sections: vec![CompactSection::MappedBlocks {
432                old_blocks: vec![0, 1],
433            }],
434            block_size: 512,
435            source_data_start: 0,
436            source_partition_offset: 0,
437        };
438        let mut reader = CompactStreamReader::new(Cursor::new(source_data), layout);
439
440        // Read 256 bytes from block 0
441        let mut buf1 = vec![0u8; 256];
442        reader.read_exact(&mut buf1).unwrap();
443        assert!(buf1.iter().all(|&b| b == 0xAA));
444
445        // Read 512 bytes across the block boundary (256 from block 0 + 256 from block 1)
446        let mut buf2 = vec![0u8; 512];
447        reader.read_exact(&mut buf2).unwrap();
448        assert!(buf2[..256].iter().all(|&b| b == 0xAA));
449        assert!(buf2[256..].iter().all(|&b| b == 0xBB));
450
451        // Read remaining 256 bytes from block 1
452        let mut buf3 = vec![0u8; 256];
453        reader.read_exact(&mut buf3).unwrap();
454        assert!(buf3.iter().all(|&b| b == 0xBB));
455    }
456
457    #[test]
458    fn test_total_size() {
459        let layout = CompactLayout {
460            sections: vec![
461                CompactSection::PreBuilt(vec![0; 100]),
462                CompactSection::MappedBlocks {
463                    old_blocks: vec![0, 1, 2],
464                },
465                CompactSection::Zeros(200),
466            ],
467            block_size: 4096,
468            source_data_start: 0,
469            source_partition_offset: 0,
470        };
471        assert_eq!(layout.total_size(), 100 + 3 * 4096 + 200);
472    }
473
474    #[test]
475    fn test_block_cache_reuse() {
476        // Map the same block twice — should use cache on second access
477        let mut source_data = vec![0u8; 512];
478        source_data.fill(0xAB);
479        let layout = CompactLayout {
480            sections: vec![CompactSection::MappedBlocks {
481                old_blocks: vec![0, 0],
482            }],
483            block_size: 512,
484            source_data_start: 0,
485            source_partition_offset: 0,
486        };
487        let mut reader = CompactStreamReader::new(Cursor::new(source_data), layout);
488        let mut buf = vec![0u8; 1024];
489        reader.read_exact(&mut buf).unwrap();
490        assert!(buf.iter().all(|&b| b == 0xAB));
491    }
492}