Skip to main content

ext4fs/
block.rs

1#![forbid(unsafe_code)]
2use crate::error::{Ext4Error, Result};
3use crate::ondisk::{GroupDescriptor, Superblock};
4use std::io::{Read, Seek, SeekFrom};
5
6#[derive(Debug)]
7pub struct BlockReader<R: Read + Seek> {
8    source: R,
9    superblock: Superblock,
10    group_descs: Vec<GroupDescriptor>,
11}
12
13impl<R: Read + Seek> BlockReader<R> {
14    pub fn open(mut source: R) -> Result<Self> {
15        // Read superblock at offset 1024
16        source.seek(SeekFrom::Start(1024))?;
17        let mut sb_buf = vec![0u8; 1024];
18        source.read_exact(&mut sb_buf)?;
19        let superblock = Superblock::parse(&sb_buf)?;
20
21        // GDT starts in block after superblock.
22        // For 1024-byte blocks, superblock occupies block 1, GDT starts at block 2.
23        // For larger blocks, both fit in block 0, GDT starts at block 1.
24        let gdt_block = if superblock.block_size == 1024 {
25            2u64
26        } else {
27            1u64
28        };
29        let gdt_offset = gdt_block * u64::from(superblock.block_size);
30        let group_count = superblock.group_count();
31        let desc_size = superblock.desc_size as usize;
32
33        // Guard against crafted images with huge group_count causing OOM before
34        // read_exact can return an EOF error.
35        let source_size = source.seek(SeekFrom::End(0))?;
36        let available = source_size.saturating_sub(gdt_offset);
37        let gdt_size = u64::from(group_count)
38            .saturating_mul(desc_size as u64)
39            .min(available);
40        if u64::from(group_count).saturating_mul(desc_size as u64) > available {
41            return Err(Ext4Error::CorruptMetadata {
42                structure: "GroupDescriptorTable",
43                detail: format!(
44                    "GDT requires {} bytes but only {available} available",
45                    u64::from(group_count).saturating_mul(desc_size as u64)
46                ),
47            });
48        }
49
50        source.seek(SeekFrom::Start(gdt_offset))?;
51        let mut gdt_buf = vec![0u8; gdt_size as usize];
52        source.read_exact(&mut gdt_buf)?;
53
54        let mut group_descs = Vec::with_capacity(group_count as usize);
55        for i in 0..group_count as usize {
56            let off = i * desc_size;
57            let gd = GroupDescriptor::parse(&gdt_buf[off..off + desc_size], superblock.desc_size)?;
58            group_descs.push(gd);
59        }
60
61        Ok(BlockReader {
62            source,
63            superblock,
64            group_descs,
65        })
66    }
67
68    pub fn superblock(&self) -> &Superblock {
69        &self.superblock
70    }
71
72    pub fn group_descriptors(&self) -> &[GroupDescriptor] {
73        &self.group_descs
74    }
75
76    pub fn group_count(&self) -> u32 {
77        self.group_descs.len() as u32
78    }
79
80    pub fn block_size(&self) -> u32 {
81        self.superblock.block_size
82    }
83
84    pub fn read_block(&mut self, block_num: u64) -> Result<Vec<u8>> {
85        if block_num >= self.superblock.blocks_count {
86            return Err(Ext4Error::BlockOutOfRange {
87                block: block_num,
88                max: self.superblock.blocks_count,
89            });
90        }
91        let offset = block_num * u64::from(self.superblock.block_size);
92        self.read_bytes(offset, self.superblock.block_size as usize)
93    }
94
95    pub fn read_blocks(&mut self, start: u64, count: u64) -> Result<Vec<u8>> {
96        let end = start.checked_add(count).ok_or(Ext4Error::BlockOutOfRange {
97            block: start,
98            max: self.superblock.blocks_count,
99        })?;
100        if end > self.superblock.blocks_count {
101            return Err(Ext4Error::BlockOutOfRange {
102                block: end - 1,
103                max: self.superblock.blocks_count,
104            });
105        }
106        let offset = start * u64::from(self.superblock.block_size);
107        let len = count as usize * self.superblock.block_size as usize;
108        self.read_bytes(offset, len)
109    }
110
111    pub fn read_bytes(&mut self, offset: u64, len: usize) -> Result<Vec<u8>> {
112        self.source.seek(SeekFrom::Start(offset))?;
113        let mut buf = vec![0u8; len];
114        self.source.read_exact(&mut buf)?;
115        Ok(buf)
116    }
117
118    pub fn group_descriptor(&self, group: u32) -> Result<&GroupDescriptor> {
119        self.group_descs
120            .get(group as usize)
121            .ok_or(Ext4Error::CorruptMetadata {
122                structure: "group_descriptor",
123                detail: format!(
124                    "group {group} out of range (max {})",
125                    self.group_descs.len()
126                ),
127            })
128    }
129
130    pub fn inode_bitmap_block(&self, group: u32) -> Result<u64> {
131        Ok(self.group_descriptor(group)?.inode_bitmap)
132    }
133
134    pub fn block_bitmap_block(&self, group: u32) -> Result<u64> {
135        Ok(self.group_descriptor(group)?.block_bitmap)
136    }
137
138    pub fn inode_table_block(&self, group: u32) -> Result<u64> {
139        Ok(self.group_descriptor(group)?.inode_table)
140    }
141}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146    use std::io::Cursor;
147
148    fn load_minimal_image() -> Option<Vec<u8>> {
149        let path = concat!(env!("CARGO_MANIFEST_DIR"), "/../tests/data/minimal.img");
150        std::fs::read(path).ok()
151    }
152
153    #[test]
154    fn open_minimal_image() {
155        let data = load_minimal_image().expect("minimal.img required");
156        let reader = BlockReader::open(Cursor::new(data)).unwrap();
157        assert_eq!(reader.superblock().magic, 0xEF53);
158        assert_eq!(reader.superblock().block_size, 4096);
159        assert!(reader.group_count() > 0);
160        assert!(!reader.group_descriptors().is_empty());
161    }
162
163    #[test]
164    fn reject_too_small_image() {
165        let data = vec![0u8; 512];
166        let err = BlockReader::open(Cursor::new(data)).unwrap_err();
167        assert!(matches!(
168            err,
169            crate::error::Ext4Error::Io(_) | crate::error::Ext4Error::TooShort { .. }
170        ));
171    }
172
173    #[test]
174    fn read_block_zero() {
175        let data = load_minimal_image().expect("minimal.img required");
176        let mut reader = BlockReader::open(Cursor::new(data)).unwrap();
177        let block = reader.read_block(0).unwrap();
178        assert_eq!(block.len(), 4096);
179    }
180
181    #[test]
182    fn read_block_out_of_range() {
183        let data = load_minimal_image().expect("minimal.img required");
184        let mut reader = BlockReader::open(Cursor::new(data)).unwrap();
185        let err = reader.read_block(u64::MAX).unwrap_err();
186        assert!(matches!(
187            err,
188            crate::error::Ext4Error::BlockOutOfRange { .. }
189        ));
190    }
191}