Skip to main content

ext4fs/ondisk/
extent.rs

1#![forbid(unsafe_code)]
2use crate::error::{Ext4Error, Result};
3
4pub const EXTENT_MAGIC: u16 = 0xF30A;
5
6// ---------------------------------------------------------------------------
7// Local LE helpers
8// ---------------------------------------------------------------------------
9
10#[inline]
11fn le16(buf: &[u8], off: usize) -> u16 {
12    u16::from_le_bytes([buf[off], buf[off + 1]])
13}
14
15#[inline]
16fn le32(buf: &[u8], off: usize) -> u32 {
17    u32::from_le_bytes([buf[off], buf[off + 1], buf[off + 2], buf[off + 3]])
18}
19
20// ---------------------------------------------------------------------------
21// ExtentHeader  (12 bytes, on-disk: ext4_extent_header)
22//   0  magic       u16 LE
23//   2  entries     u16 LE
24//   4  max         u16 LE
25//   6  depth       u16 LE
26//   8  generation  u32 LE
27// ---------------------------------------------------------------------------
28
29#[derive(Debug, Clone, PartialEq, Eq)]
30pub struct ExtentHeader {
31    pub magic: u16,
32    pub entries: u16,
33    pub max: u16,
34    pub depth: u16,
35    pub generation: u32,
36}
37
38impl ExtentHeader {
39    pub fn parse(buf: &[u8]) -> Result<Self> {
40        if buf.len() < 12 {
41            return Err(Ext4Error::TooShort {
42                structure: "ExtentHeader",
43                expected: 12,
44                found: buf.len(),
45            });
46        }
47        let magic = le16(buf, 0);
48        if magic != EXTENT_MAGIC {
49            return Err(Ext4Error::CorruptMetadata {
50                structure: "ExtentHeader",
51                detail: format!("bad magic: 0x{magic:04X}"),
52            });
53        }
54        Ok(Self {
55            magic,
56            entries: le16(buf, 2),
57            max: le16(buf, 4),
58            depth: le16(buf, 6),
59            generation: le32(buf, 8),
60        })
61    }
62}
63
64// ---------------------------------------------------------------------------
65// ExtentLeaf  (12 bytes, on-disk: ext4_extent)
66//   0  ee_block     u32 LE  — first logical block
67//   4  ee_len       u16 LE  — bit 15: unwritten; bits 14-0: length
68//   6  ee_start_hi  u16 LE  — high 16 bits of physical block
69//   8  ee_start_lo  u32 LE  — low 32 bits of physical block
70// ---------------------------------------------------------------------------
71
72#[derive(Debug, Clone, PartialEq, Eq)]
73pub struct ExtentLeaf {
74    pub logical_block: u32,
75    pub length: u16,
76    pub physical_block: u64,
77    pub unwritten: bool,
78}
79
80impl ExtentLeaf {
81    pub fn parse(buf: &[u8]) -> Self {
82        let logical_block = le32(buf, 0);
83        let ee_len = le16(buf, 4);
84        let start_hi = u64::from(le16(buf, 6));
85        let start_lo = u64::from(le32(buf, 8));
86
87        let unwritten = (ee_len & 0x8000) != 0;
88        let length = ee_len & 0x7FFF;
89        let physical_block = (start_hi << 32) | start_lo;
90
91        Self {
92            logical_block,
93            length,
94            physical_block,
95            unwritten,
96        }
97    }
98}
99
100// ---------------------------------------------------------------------------
101// ExtentIndex  (12 bytes, on-disk: ext4_extent_idx)
102//   0  ei_block    u32 LE  — first logical block covered by this subtree
103//   4  ei_leaf_lo  u32 LE  — low 32 bits of child block
104//   8  ei_leaf_hi  u16 LE  — high 16 bits of child block
105//  10  (unused)    u16
106// ---------------------------------------------------------------------------
107
108#[derive(Debug, Clone, PartialEq, Eq)]
109pub struct ExtentIndex {
110    pub logical_block: u32,
111    pub child_block: u64,
112}
113
114impl ExtentIndex {
115    pub fn parse(buf: &[u8]) -> Self {
116        let logical_block = le32(buf, 0);
117        let leaf_lo = u64::from(le32(buf, 4));
118        let leaf_hi = u64::from(le16(buf, 8));
119        let child_block = (leaf_hi << 32) | leaf_lo;
120        Self {
121            logical_block,
122            child_block,
123        }
124    }
125}
126
127// ---------------------------------------------------------------------------
128// Tests
129// ---------------------------------------------------------------------------
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134
135    #[test]
136    fn parse_extent_header() {
137        let mut buf = vec![0u8; 12];
138        buf[0] = 0x0A;
139        buf[1] = 0xF3; // magic 0xF30A
140        buf[2] = 3; // entries
141        buf[4] = 4; // max
142        buf[6] = 0; // depth (leaf)
143        let hdr = ExtentHeader::parse(&buf).unwrap();
144        assert_eq!(hdr.magic, EXTENT_MAGIC);
145        assert_eq!(hdr.entries, 3);
146        assert_eq!(hdr.max, 4);
147        assert_eq!(hdr.depth, 0);
148    }
149
150    #[test]
151    fn reject_bad_magic() {
152        let buf = vec![0u8; 12];
153        let err = ExtentHeader::parse(&buf).unwrap_err();
154        assert!(matches!(
155            err,
156            crate::error::Ext4Error::CorruptMetadata { .. }
157        ));
158    }
159
160    #[test]
161    fn parse_extent_leaf() {
162        let mut buf = vec![0u8; 12];
163        buf[4] = 10; // ee_len = 10
164        buf[8] = 0xF4;
165        buf[9] = 0x01; // ee_start_lo = 500 (0x01F4)
166        let leaf = ExtentLeaf::parse(&buf);
167        assert_eq!(leaf.logical_block, 0);
168        assert_eq!(leaf.length, 10);
169        assert_eq!(leaf.physical_block, 500);
170        assert!(!leaf.unwritten);
171    }
172
173    #[test]
174    fn extent_leaf_unwritten_flag() {
175        let mut buf = vec![0u8; 12];
176        buf[4] = 0x05;
177        buf[5] = 0x80; // ee_len with bit 15 set
178        let leaf = ExtentLeaf::parse(&buf);
179        assert_eq!(leaf.length, 5);
180        assert!(leaf.unwritten);
181    }
182
183    #[test]
184    fn parse_extent_index() {
185        let mut buf = vec![0u8; 12];
186        buf[0] = 0xE8;
187        buf[1] = 0x03; // ei_block = 1000
188        buf[4] = 0xD0;
189        buf[5] = 0x07; // ei_leaf_lo = 2000
190        buf[8] = 1; // ei_leaf_hi = 1
191        let idx = ExtentIndex::parse(&buf);
192        assert_eq!(idx.logical_block, 1000);
193        assert_eq!(idx.child_block, (1u64 << 32) | 0x7D0);
194    }
195}