1use crate::block_io::BlockDevice;
9use crate::checksum::Checksummer;
10use crate::error::{Error, Result};
11use crate::superblock::Superblock;
12
13#[derive(Debug, Clone, Copy)]
15pub struct BlockGroupDescriptor {
16 pub block_bitmap: u64,
17 pub inode_bitmap: u64,
18 pub inode_table: u64,
19 pub free_blocks_count: u32,
20 pub free_inodes_count: u32,
21 pub used_dirs_count: u32,
22 pub flags: u16,
23 pub itable_unused: u32,
24 pub block_bitmap_csum: u32,
25 pub inode_bitmap_csum: u32,
26 pub checksum: u16,
27}
28
29pub(crate) const OFF_FREE_BLOCKS_LO: usize = 0x0C;
34pub(crate) const OFF_FREE_INODES_LO: usize = 0x0E;
35pub(crate) const OFF_USED_DIRS_LO: usize = 0x10;
36pub(crate) const OFF_FREE_BLOCKS_HI: usize = 0x2C;
37pub(crate) const OFF_FREE_INODES_HI: usize = 0x2E;
38pub(crate) const OFF_USED_DIRS_HI: usize = 0x30;
39
40fn u16_at(buf: &[u8], off: usize) -> u16 {
42 u16::from_le_bytes(buf[off..off + 2].try_into().unwrap())
43}
44
45bitflags::bitflags! {
46 #[derive(Debug, Clone, Copy)]
48 pub struct BgdFlags: u16 {
49 const INODE_UNINIT = 0x0001;
51 const BLOCK_UNINIT = 0x0002;
53 const ITABLE_ZEROED = 0x0004;
55 }
56}
57
58impl BlockGroupDescriptor {
59 pub fn parse(buf: &[u8], desc_size: u16) -> Result<Self> {
61 if buf.len() < desc_size as usize {
62 return Err(Error::Corrupt("bgd buffer too small"));
63 }
64
65 let block_bitmap_lo = u32::from_le_bytes(buf[0x00..0x04].try_into().unwrap());
66 let inode_bitmap_lo = u32::from_le_bytes(buf[0x04..0x08].try_into().unwrap());
67 let inode_table_lo = u32::from_le_bytes(buf[0x08..0x0C].try_into().unwrap());
68 let free_blocks_lo = u16_at(buf, OFF_FREE_BLOCKS_LO);
69 let free_inodes_lo = u16_at(buf, OFF_FREE_INODES_LO);
70 let used_dirs_lo = u16_at(buf, OFF_USED_DIRS_LO);
71 let flags = u16::from_le_bytes(buf[0x12..0x14].try_into().unwrap());
72 let block_bitmap_csum_lo = u16::from_le_bytes(buf[0x18..0x1A].try_into().unwrap());
73 let inode_bitmap_csum_lo = u16::from_le_bytes(buf[0x1A..0x1C].try_into().unwrap());
74 let itable_unused_lo = u16::from_le_bytes(buf[0x1C..0x1E].try_into().unwrap());
75 let checksum = u16::from_le_bytes(buf[0x1E..0x20].try_into().unwrap());
76
77 let (
78 block_bitmap_hi,
79 inode_bitmap_hi,
80 inode_table_hi,
81 free_blocks_hi,
82 free_inodes_hi,
83 used_dirs_hi,
84 itable_unused_hi,
85 block_bitmap_csum_hi,
86 inode_bitmap_csum_hi,
87 ) = if desc_size >= 64 {
88 (
89 u32::from_le_bytes(buf[0x20..0x24].try_into().unwrap()),
90 u32::from_le_bytes(buf[0x24..0x28].try_into().unwrap()),
91 u32::from_le_bytes(buf[0x28..0x2C].try_into().unwrap()),
92 u16_at(buf, OFF_FREE_BLOCKS_HI),
93 u16_at(buf, OFF_FREE_INODES_HI),
94 u16_at(buf, OFF_USED_DIRS_HI),
95 u16::from_le_bytes(buf[0x32..0x34].try_into().unwrap()),
96 u16::from_le_bytes(buf[0x38..0x3A].try_into().unwrap()),
97 u16::from_le_bytes(buf[0x3A..0x3C].try_into().unwrap()),
98 )
99 } else {
100 (0, 0, 0, 0, 0, 0, 0, 0, 0)
101 };
102
103 Ok(Self {
104 block_bitmap: ((block_bitmap_hi as u64) << 32) | block_bitmap_lo as u64,
105 inode_bitmap: ((inode_bitmap_hi as u64) << 32) | inode_bitmap_lo as u64,
106 inode_table: ((inode_table_hi as u64) << 32) | inode_table_lo as u64,
107 free_blocks_count: ((free_blocks_hi as u32) << 16) | free_blocks_lo as u32,
108 free_inodes_count: ((free_inodes_hi as u32) << 16) | free_inodes_lo as u32,
109 used_dirs_count: ((used_dirs_hi as u32) << 16) | used_dirs_lo as u32,
110 flags,
111 itable_unused: ((itable_unused_hi as u32) << 16) | itable_unused_lo as u32,
112 block_bitmap_csum: ((block_bitmap_csum_hi as u32) << 16) | block_bitmap_csum_lo as u32,
113 inode_bitmap_csum: ((inode_bitmap_csum_hi as u32) << 16) | inode_bitmap_csum_lo as u32,
114 checksum,
115 })
116 }
117
118 pub fn flags(&self) -> BgdFlags {
119 BgdFlags::from_bits_truncate(self.flags)
120 }
121}
122
123pub fn read_all<D: BlockDevice + ?Sized>(
128 dev: &D,
129 sb: &Superblock,
130 csum: &Checksummer,
131) -> Result<Vec<BlockGroupDescriptor>> {
132 let block_size = sb.block_size() as u64;
133
134 let group_count = sb.block_group_count();
135 let total_bytes = group_count
146 .checked_mul(sb.desc_size as u64)
147 .ok_or(Error::Corrupt(
148 "superblock: the group descriptor table's size overflows",
149 ))?;
150 if total_bytes > dev.size_bytes() {
151 return Err(Error::Corrupt(
152 "superblock: the group descriptor table reaches past the end of the device",
153 ));
154 }
155
156 let mut table_block: Option<(u64, Vec<u8>)> = None;
160 let group_count: usize = table_len(group_count)?;
161 let mut groups = Vec::with_capacity(group_count);
162 for i in 0..group_count {
163 let (block, off) = sb.descriptor_location(i as u64);
164 if table_block.as_ref().map(|(b, _)| *b) != Some(block) {
165 let end = block
166 .checked_add(1)
167 .and_then(|b| b.checked_mul(block_size))
168 .ok_or(Error::Corrupt(
169 "superblock: the group descriptor table's extent overflows",
170 ))?;
171 if end > dev.size_bytes() {
172 return Err(Error::Corrupt(
173 "superblock: the group descriptor table reaches past the end of the device",
174 ));
175 }
176 let mut bytes = vec![0u8; block_size as usize];
177 dev.read_at(block * block_size, &mut bytes)?;
178 table_block = Some((block, bytes));
179 }
180 let bytes = &table_block.as_ref().unwrap().1;
181 let raw = &bytes[off..off + sb.desc_size as usize];
182 let stored = u16::from_le_bytes([raw[0x1E], raw[0x1F]]);
183 if crate::checksum::group_desc_csum(sb, csum, i as u32, raw)
184 .is_some_and(|want| want != stored)
185 {
186 return Err(Error::BadChecksum {
187 what: "block group descriptor",
188 });
189 }
190 let bgd = BlockGroupDescriptor::parse(raw, sb.desc_size)?;
191 let table_blocks = (u64::from(sb.inodes_per_group) * u64::from(sb.inode_size))
209 .div_ceil(u64::from(sb.block_size()));
210 let past_the_end = bgd.block_bitmap >= sb.blocks_count
211 || bgd.inode_bitmap >= sb.blocks_count
212 || bgd
213 .inode_table
214 .checked_add(table_blocks)
215 .is_none_or(|end| end > sb.blocks_count);
216 if past_the_end {
217 return Err(Error::Corrupt(
218 "a block group descriptor points outside the filesystem",
219 ));
220 }
221 check_pointers_within(sb, i as u64, &bgd, table_blocks)?;
222 groups.push(bgd);
223 }
224 Ok(groups)
225}
226
227fn check_pointers_within(
245 sb: &Superblock,
246 group: u64,
247 bgd: &BlockGroupDescriptor,
248 table_blocks: u64,
249) -> Result<()> {
250 let superblock_block = crate::superblock::SUPERBLOCK_OFFSET / u64::from(sb.block_size());
254 let group0_metadata_end = superblock_block + sb.group_head_metadata_blocks(0);
255 let table_last = bgd.inode_table + table_blocks.max(1) - 1;
256 if bgd.block_bitmap < group0_metadata_end
257 || bgd.inode_bitmap < group0_metadata_end
258 || bgd.inode_table < group0_metadata_end
259 {
260 return Err(Error::Corrupt(
261 "a block group descriptor points into group 0's superblock or descriptor table",
262 ));
263 }
264 if sb.feature_incompat & crate::features::Incompat::FLEX_BG.bits() != 0 {
265 return Ok(());
266 }
267 let first = u64::from(sb.first_data_block) + group * u64::from(sb.blocks_per_group);
268 let last = first
269 .saturating_add(u64::from(sb.blocks_per_group))
270 .min(sb.blocks_count)
271 - 1;
272 let within = |b: u64| (first..=last).contains(&b);
273 if !(within(bgd.block_bitmap) && within(bgd.inode_bitmap) && within(bgd.inode_table))
274 || table_last > last
275 {
276 return Err(Error::Corrupt(
277 "a block group descriptor points outside its own group, without flex_bg",
278 ));
279 }
280 Ok(())
281}
282
283fn table_len<T: TryFrom<u64>>(group_count: u64) -> Result<T> {
289 T::try_from(group_count).map_err(|_| {
290 Error::Corrupt("superblock: the group count does not fit in this target's address space")
291 })
292}
293
294pub fn locate_inode(
297 sb: &Superblock,
298 groups: &[BlockGroupDescriptor],
299 ino: u32,
300) -> Result<(u64, u32)> {
301 if ino == 0 || ino > sb.inodes_count {
302 return Err(Error::InvalidInode(ino));
303 }
304 if sb.inodes_per_group == 0 || sb.inode_size == 0 {
305 return Err(Error::Corrupt(
306 "superblock: zero inodes_per_group or inode_size",
307 ));
308 }
309 let group_idx = ((ino - 1) / sb.inodes_per_group) as usize;
310 let local_idx = ((ino - 1) % sb.inodes_per_group) as u64;
311
312 let bgd = groups.get(group_idx).ok_or(Error::InvalidInode(ino))?;
313 let block_size = sb.block_size() as u64;
314 let inode_size = sb.inode_size as u64;
315 if inode_size > block_size {
316 return Err(Error::Corrupt(
317 "superblock: inode_size larger than block_size",
318 ));
319 }
320 let inodes_per_block = block_size / inode_size;
321 if inodes_per_block == 0 {
322 return Err(Error::Corrupt("superblock: inode_size exceeds block_size"));
323 }
324
325 let block = bgd
326 .inode_table
327 .checked_add(local_idx / inodes_per_block)
328 .ok_or(Error::Corrupt("inode table block number overflow"))?;
329 let off_bytes = (local_idx % inodes_per_block) * inode_size;
330 let offset_in_block: u32 = off_bytes
331 .try_into()
332 .map_err(|_| Error::Corrupt("inode offset exceeds u32"))?;
333
334 Ok((block, offset_in_block))
335}
336
337#[cfg(test)]
338mod tests {
339 use super::*;
340
341 #[test]
346 fn a_group_count_past_the_target_width_is_refused() {
347 assert!(table_len::<u32>(u64::from(u32::MAX) + 1).is_err());
348 assert!(table_len::<u32>(u64::MAX).is_err());
349 assert_eq!(table_len::<u32>(u64::from(u32::MAX)).unwrap(), u32::MAX);
350 assert_eq!(table_len::<usize>(3).unwrap(), 3);
351 }
352}