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 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 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 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}