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