Skip to main content

arcbox_ext4/
extent.rs

1// Extent tree creation and parsing.
2//
3// Write path: build the extent tree for an inode's data blocks and serialize
4// it into the inode's `block` field (and overflow blocks when needed).
5//
6// Read path: parse an extent tree from an inode's `block` field, returning
7// physical block ranges.
8
9use crate::constants::*;
10use crate::file_tree::BlockRange;
11use crate::types::*;
12use std::io::{self, Read, Seek, SeekFrom, Write};
13
14// ---------------------------------------------------------------------------
15// Helpers
16// ---------------------------------------------------------------------------
17
18/// Round `n` up to the next multiple of `align`.
19#[inline]
20fn div_ceil(n: u32, d: u32) -> u32 {
21    (n + d - 1) / d
22}
23
24/// Build extent leaves that cover `num_blocks` starting at physical block
25/// `start`, using at most `MAX_BLOCKS_PER_EXTENT` blocks per extent.
26/// `offset` is the starting logical block number.
27fn fill_extent_leaves(start: u32, num_blocks: u32, num_extents: u32, offset: u32) -> Vec<ExtentLeaf> {
28    let mut leaves = Vec::with_capacity(num_extents as usize);
29    let mut remaining = num_blocks;
30    let mut phys = start;
31    let mut logical = offset;
32
33    for _ in 0..num_extents {
34        let len = remaining.min(MAX_BLOCKS_PER_EXTENT);
35        leaves.push(ExtentLeaf {
36            block: logical,
37            len: len as u16,
38            start_hi: 0,
39            start_lo: phys,
40        });
41        phys += len;
42        logical += len;
43        remaining -= len;
44    }
45
46    leaves
47}
48
49// ---------------------------------------------------------------------------
50// Write path (Formatter)
51// ---------------------------------------------------------------------------
52
53/// Write the extent tree for an inode's data blocks into the inode's `block`
54/// field.
55///
56/// If the file spans more than 4 extents, overflow extent blocks are written
57/// to `writer` and the inode's block field contains index entries pointing to
58/// them.
59///
60/// Updates `inode.block`, `inode.blocks_lo`, and `inode.flags` in place.
61pub fn write_extents<W: Write + Seek>(
62    inode: &mut Inode,
63    blocks: BlockRange,
64    block_size: u32,
65    writer: &mut W,
66    current_block: &mut u32,
67) -> io::Result<()> {
68    let data_blocks = blocks.end - blocks.start;
69    if data_blocks == 0 {
70        return Ok(());
71    }
72
73    let num_extents = div_ceil(data_blocks, MAX_BLOCKS_PER_EXTENT);
74
75    // The inode's block field is 60 bytes = header(12) + 4 * entry(12).
76    // So we can fit up to 4 extents inline.
77    let max_inline = 4u32;
78
79    if num_extents <= max_inline {
80        // Case: 1-4 extents -- everything fits in the inode's block field.
81        write_inline_extents(inode, blocks.start, data_blocks, num_extents, block_size);
82    } else {
83        // Case: 5+ extents -- depth-1 tree with index entries in the inode
84        // and leaf blocks written to the output.
85        write_indexed_extents(inode, blocks.start, data_blocks, num_extents, block_size, writer, current_block)?;
86    }
87
88    Ok(())
89}
90
91/// Write extent tree inline (depth 0): header + leaves directly in inode.block.
92fn write_inline_extents(
93    inode: &mut Inode,
94    start: u32,
95    data_blocks: u32,
96    num_extents: u32,
97    block_size: u32,
98) {
99    let leaves = fill_extent_leaves(start, data_blocks, num_extents, 0);
100
101    let mut buf = [0u8; INODE_BLOCK_SIZE];
102
103    // Write extent header.
104    let header = ExtentHeader {
105        magic: EXTENT_HEADER_MAGIC,
106        entries: num_extents as u16,
107        max: 4,
108        depth: 0,
109        generation: 0,
110    };
111    header.write_to(&mut buf[..ExtentHeader::SIZE]);
112
113    // Write leaf entries.
114    for (i, leaf) in leaves.iter().enumerate() {
115        let off = ExtentHeader::SIZE + i * ExtentLeaf::SIZE;
116        leaf.write_to(&mut buf[off..off + ExtentLeaf::SIZE]);
117    }
118
119    inode.block = buf;
120
121    // When the HUGE_FILE inode flag is set (and the ro_compat HUGE_FILE
122    // feature is enabled on the filesystem), blocks_lo counts in units of
123    // filesystem blocks.  Otherwise it counts 512-byte sectors.
124    if inode.flags & inode_flags::HUGE_FILE != 0 {
125        inode.blocks_lo = data_blocks;
126    } else {
127        let sectors = (data_blocks as u64) * (block_size as u64 / 512);
128        inode.blocks_lo = sectors as u32;
129    }
130    inode.flags |= inode_flags::EXTENTS;
131}
132
133/// Write extent tree with depth 1: index entries in inode.block, leaf blocks
134/// written to the output stream.
135fn write_indexed_extents<W: Write + Seek>(
136    inode: &mut Inode,
137    start: u32,
138    data_blocks: u32,
139    num_extents: u32,
140    block_size: u32,
141    writer: &mut W,
142    current_block: &mut u32,
143) -> io::Result<()> {
144    // How many leaf entries fit in one extent block?
145    // Block layout: header(12) + N * leaf(12) + tail(4).
146    // N = (block_size - 12 - 4) / 12
147    let leaves_per_block = (block_size as usize - ExtentHeader::SIZE - ExtentTail::SIZE) / ExtentLeaf::SIZE;
148
149    // How many index blocks do we need?
150    let num_index_blocks = div_ceil(num_extents, leaves_per_block as u32);
151
152    // The inode can hold up to 4 index entries (same 60-byte limit).
153    debug_assert!(num_index_blocks <= 4, "files requiring >4 index blocks (depth>1) are not supported");
154
155    let all_leaves = fill_extent_leaves(start, data_blocks, num_extents, 0);
156
157    // Allocate block numbers for the index (leaf-block) nodes.
158    // They are written sequentially starting at *current_block.
159    let index_block_start = *current_block;
160
161    // Build the index entries.
162    let mut indices = Vec::with_capacity(num_index_blocks as usize);
163    for i in 0..num_index_blocks as usize {
164        let first_leaf_in_block = i * leaves_per_block;
165        let logical_block = all_leaves[first_leaf_in_block].block;
166        indices.push(ExtentIndex {
167            block: logical_block,
168            leaf_lo: index_block_start + i as u32,
169            leaf_hi: 0,
170            unused: 0,
171        });
172    }
173
174    // Write the inode's block field: header + index entries.
175    let mut buf = [0u8; INODE_BLOCK_SIZE];
176    let header = ExtentHeader {
177        magic: EXTENT_HEADER_MAGIC,
178        entries: num_index_blocks as u16,
179        max: 4,
180        depth: 1,
181        generation: 0,
182    };
183    header.write_to(&mut buf[..ExtentHeader::SIZE]);
184
185    for (i, idx) in indices.iter().enumerate() {
186        let off = ExtentHeader::SIZE + i * ExtentIndex::SIZE;
187        idx.write_to(&mut buf[off..off + ExtentIndex::SIZE]);
188    }
189
190    inode.block = buf;
191
192    // Write the leaf blocks to the output.
193    for i in 0..num_index_blocks as usize {
194        let leaf_start = i * leaves_per_block;
195        let leaf_end = ((i + 1) * leaves_per_block).min(all_leaves.len());
196        let block_leaves = &all_leaves[leaf_start..leaf_end];
197
198        let mut block_buf = vec![0u8; block_size as usize];
199
200        // Header for this leaf block.
201        let leaf_header = ExtentHeader {
202            magic: EXTENT_HEADER_MAGIC,
203            entries: block_leaves.len() as u16,
204            max: leaves_per_block as u16,
205            depth: 0,
206            generation: 0,
207        };
208        leaf_header.write_to(&mut block_buf[..ExtentHeader::SIZE]);
209
210        // Leaf entries.
211        for (j, leaf) in block_leaves.iter().enumerate() {
212            let off = ExtentHeader::SIZE + j * ExtentLeaf::SIZE;
213            leaf.write_to(&mut block_buf[off..off + ExtentLeaf::SIZE]);
214        }
215
216        // Tail checksum (zeroed -- no metadata checksumming in this implementation).
217        let tail = ExtentTail { checksum: 0 };
218        let tail_off = block_size as usize - ExtentTail::SIZE;
219        tail.write_to(&mut block_buf[tail_off..tail_off + ExtentTail::SIZE]);
220
221        // Seek to the correct block position and write.
222        let byte_offset = (*current_block as u64) * (block_size as u64);
223        writer.seek(SeekFrom::Start(byte_offset))?;
224        writer.write_all(&block_buf)?;
225
226        *current_block += 1;
227    }
228
229    // blocks_lo accounts for data blocks plus the index (metadata) blocks.
230    let total_blocks = data_blocks + num_index_blocks;
231    if inode.flags & inode_flags::HUGE_FILE != 0 {
232        inode.blocks_lo = total_blocks;
233    } else {
234        let sectors = (total_blocks as u64) * (block_size as u64 / 512);
235        inode.blocks_lo = sectors as u32;
236    }
237    inode.flags |= inode_flags::EXTENTS;
238
239    Ok(())
240}
241
242// ---------------------------------------------------------------------------
243// Read path (Reader)
244// ---------------------------------------------------------------------------
245
246/// Parse the extent tree from an inode's `block` field.
247///
248/// Returns a list of `(physical_start, physical_end)` block ranges covering
249/// the file's data.  Supports depth 0 (inline leaves) and depth 1 (one level
250/// of index nodes).
251pub fn parse_extents<R: Read + Seek>(
252    inode: &Inode,
253    block_size: u64,
254    reader: &mut R,
255) -> Result<Vec<(u32, u32)>, crate::error::ReadError> {
256    let header = ExtentHeader::read_from(&inode.block);
257
258    if header.magic != EXTENT_HEADER_MAGIC {
259        // No valid extent tree (e.g. fast symlinks store the target directly
260        // in the block field).  Return an empty list, matching Apple's behavior.
261        return Ok(Vec::new());
262    }
263
264    match header.depth {
265        0 => parse_depth0(&inode.block, &header),
266        1 => parse_depth1(&inode.block, &header, block_size, reader),
267        _ => Err(crate::error::ReadError::DeepExtentsUnsupported),
268    }
269}
270
271/// Parse inline leaf entries (depth 0).
272fn parse_depth0(
273    block_field: &[u8],
274    header: &ExtentHeader,
275) -> Result<Vec<(u32, u32)>, crate::error::ReadError> {
276    let mut ranges = Vec::with_capacity(header.entries as usize);
277
278    for i in 0..header.entries as usize {
279        let off = ExtentHeader::SIZE + i * ExtentLeaf::SIZE;
280        if off + ExtentLeaf::SIZE > block_field.len() {
281            return Err(crate::error::ReadError::InvalidExtents);
282        }
283        let leaf = ExtentLeaf::read_from(&block_field[off..]);
284        let phys_start = leaf.start_lo;
285        let phys_end = phys_start + leaf.len as u32;
286        ranges.push((phys_start, phys_end));
287    }
288
289    Ok(ranges)
290}
291
292/// Parse depth-1 extent tree: read index entries from the inode, then read
293/// each leaf block from disk.
294fn parse_depth1<R: Read + Seek>(
295    block_field: &[u8],
296    header: &ExtentHeader,
297    block_size: u64,
298    reader: &mut R,
299) -> Result<Vec<(u32, u32)>, crate::error::ReadError> {
300    let mut ranges = Vec::new();
301
302    for i in 0..header.entries as usize {
303        let off = ExtentHeader::SIZE + i * ExtentIndex::SIZE;
304        if off + ExtentIndex::SIZE > block_field.len() {
305            return Err(crate::error::ReadError::InvalidExtents);
306        }
307        let index = ExtentIndex::read_from(&block_field[off..]);
308
309        // Read the leaf block from disk.
310        let phys_block = index.leaf() as u64;
311        let byte_offset = phys_block * block_size;
312
313        reader.seek(SeekFrom::Start(byte_offset))
314            .map_err(|_| crate::error::ReadError::CouldNotReadBlock(phys_block as u32))?;
315
316        let mut leaf_buf = vec![0u8; block_size as usize];
317        reader.read_exact(&mut leaf_buf)
318            .map_err(|_| crate::error::ReadError::CouldNotReadBlock(phys_block as u32))?;
319
320        let leaf_header = ExtentHeader::read_from(&leaf_buf);
321        if leaf_header.magic != EXTENT_HEADER_MAGIC || leaf_header.depth != 0 {
322            return Err(crate::error::ReadError::InvalidExtents);
323        }
324
325        for j in 0..leaf_header.entries as usize {
326            let leaf_off = ExtentHeader::SIZE + j * ExtentLeaf::SIZE;
327            if leaf_off + ExtentLeaf::SIZE > leaf_buf.len() {
328                return Err(crate::error::ReadError::InvalidExtents);
329            }
330            let leaf = ExtentLeaf::read_from(&leaf_buf[leaf_off..]);
331            let phys_start = leaf.start_lo;
332            let phys_end = phys_start + leaf.len as u32;
333            ranges.push((phys_start, phys_end));
334        }
335    }
336
337    Ok(ranges)
338}
339
340#[cfg(test)]
341mod tests {
342    use super::*;
343    use std::io::Cursor;
344
345    #[test]
346    fn test_fill_extent_leaves_single() {
347        let leaves = fill_extent_leaves(100, 10, 1, 0);
348        assert_eq!(leaves.len(), 1);
349        assert_eq!(leaves[0].block, 0);
350        assert_eq!(leaves[0].start_lo, 100);
351        assert_eq!(leaves[0].len, 10);
352    }
353
354    #[test]
355    fn test_fill_extent_leaves_multiple() {
356        // 0x8000 * 2 + 5 = 65541 blocks requiring 3 extents.
357        let num_blocks = MAX_BLOCKS_PER_EXTENT * 2 + 5;
358        let leaves = fill_extent_leaves(0, num_blocks, 3, 0);
359        assert_eq!(leaves.len(), 3);
360        assert_eq!(leaves[0].len, MAX_BLOCKS_PER_EXTENT as u16);
361        assert_eq!(leaves[1].len, MAX_BLOCKS_PER_EXTENT as u16);
362        assert_eq!(leaves[2].len, 5);
363        assert_eq!(leaves[1].start_lo, MAX_BLOCKS_PER_EXTENT);
364        assert_eq!(leaves[2].start_lo, MAX_BLOCKS_PER_EXTENT * 2);
365    }
366
367    #[test]
368    fn test_inline_extents_roundtrip() {
369        let block_size = 4096u32;
370        let mut inode = Inode::default();
371        let blocks = BlockRange { start: 50, end: 60 };
372
373        let mut cursor = Cursor::new(Vec::new());
374        let mut current_block = 100u32;
375
376        write_extents(&mut inode, blocks, block_size, &mut cursor, &mut current_block).unwrap();
377
378        // Should have written nothing to the cursor (all inline).
379        assert_eq!(cursor.get_ref().len(), 0);
380
381        // Parse back.
382        let ranges = parse_extents(&inode, block_size as u64, &mut cursor).unwrap();
383        assert_eq!(ranges.len(), 1);
384        assert_eq!(ranges[0], (50, 60));
385    }
386
387    #[test]
388    fn test_zero_blocks_is_noop() {
389        let mut inode = Inode::default();
390        let blocks = BlockRange { start: 0, end: 0 };
391        let mut cursor = Cursor::new(Vec::new());
392        let mut current_block = 0u32;
393
394        write_extents(&mut inode, blocks, 4096, &mut cursor, &mut current_block).unwrap();
395        assert_eq!(inode.flags & inode_flags::EXTENTS, 0);
396    }
397
398    /// Depth-1 extent tree roundtrip: 5+ extents require an indexed tree
399    /// because the inode's 60-byte block field only fits 4 inline leaf entries.
400    /// We write the extents, then parse them back and verify the ranges match.
401    #[test]
402    fn test_depth1_extent_tree_roundtrip() {
403        let block_size = 4096u32;
404        // 5 extents requires 5 * MAX_BLOCKS_PER_EXTENT data blocks.
405        let data_blocks = MAX_BLOCKS_PER_EXTENT * 5;
406        let phys_start = 200u32;
407
408        let mut inode = Inode::default();
409        let blocks = BlockRange {
410            start: phys_start,
411            end: phys_start + data_blocks,
412        };
413
414        // Allocate a cursor large enough for the index (leaf) blocks.
415        // The index blocks are written at current_block * block_size.
416        // We need at most a few blocks for the tree, so 1 MiB is plenty.
417        let backing = vec![0u8; 1024 * 1024];
418        let mut cursor = Cursor::new(backing);
419        // Set current_block high enough that the index blocks don't overlap
420        // with the range [phys_start .. phys_start + data_blocks].
421        let mut current_block = phys_start + data_blocks + 10;
422
423        write_extents(
424            &mut inode,
425            blocks,
426            block_size,
427            &mut cursor,
428            &mut current_block,
429        )
430        .unwrap();
431
432        // The inode should have the EXTENTS flag.
433        assert_ne!(inode.flags & inode_flags::EXTENTS, 0);
434
435        // The header in the inode should have depth 1.
436        let header = ExtentHeader::read_from(&inode.block);
437        assert_eq!(header.magic, EXTENT_HEADER_MAGIC);
438        assert_eq!(header.depth, 1);
439        // There should be at least 1 index entry.
440        assert!(header.entries >= 1);
441
442        // Parse back and verify we get 5 ranges, each MAX_BLOCKS_PER_EXTENT long.
443        let ranges = parse_extents(&inode, block_size as u64, &mut cursor).unwrap();
444        assert_eq!(ranges.len(), 5);
445
446        let mut expected_phys = phys_start;
447        for (i, &(start, end)) in ranges.iter().enumerate() {
448            assert_eq!(
449                start, expected_phys,
450                "extent {} start mismatch: expected {} got {}",
451                i, expected_phys, start
452            );
453            assert_eq!(
454                end - start,
455                MAX_BLOCKS_PER_EXTENT,
456                "extent {} length mismatch",
457                i
458            );
459            expected_phys += MAX_BLOCKS_PER_EXTENT;
460        }
461    }
462
463    /// Depth-1 with a non-even split: 6 extents where the last is shorter.
464    #[test]
465    fn test_depth1_extent_tree_uneven() {
466        let block_size = 4096u32;
467        let extra = 42u32;
468        let data_blocks = MAX_BLOCKS_PER_EXTENT * 5 + extra;
469        let phys_start = 100u32;
470
471        let mut inode = Inode::default();
472        let blocks = BlockRange {
473            start: phys_start,
474            end: phys_start + data_blocks,
475        };
476
477        let backing = vec![0u8; 1024 * 1024];
478        let mut cursor = Cursor::new(backing);
479        let mut current_block = phys_start + data_blocks + 10;
480
481        write_extents(
482            &mut inode,
483            blocks,
484            block_size,
485            &mut cursor,
486            &mut current_block,
487        )
488        .unwrap();
489
490        let ranges = parse_extents(&inode, block_size as u64, &mut cursor).unwrap();
491        assert_eq!(ranges.len(), 6);
492
493        // First 5 extents should be full-size.
494        for i in 0..5 {
495            assert_eq!(
496                ranges[i].1 - ranges[i].0,
497                MAX_BLOCKS_PER_EXTENT,
498                "extent {} should be full",
499                i
500            );
501        }
502        // Last extent should be the remainder.
503        assert_eq!(ranges[5].1 - ranges[5].0, extra);
504
505        // Verify physical contiguity.
506        let mut expected_phys = phys_start;
507        for &(start, end) in &ranges {
508            assert_eq!(start, expected_phys);
509            expected_phys = end;
510        }
511    }
512}