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
29bitflags::bitflags! {
30 #[derive(Debug, Clone, Copy)]
32 pub struct BgdFlags: u16 {
33 const INODE_UNINIT = 0x0001;
35 const BLOCK_UNINIT = 0x0002;
37 const ITABLE_ZEROED = 0x0004;
39 }
40}
41
42impl BlockGroupDescriptor {
43 pub fn parse(buf: &[u8], desc_size: u16) -> Result<Self> {
45 if buf.len() < desc_size as usize {
46 return Err(Error::Corrupt("bgd buffer too small"));
47 }
48
49 let block_bitmap_lo = u32::from_le_bytes(buf[0x00..0x04].try_into().unwrap());
50 let inode_bitmap_lo = u32::from_le_bytes(buf[0x04..0x08].try_into().unwrap());
51 let inode_table_lo = u32::from_le_bytes(buf[0x08..0x0C].try_into().unwrap());
52 let free_blocks_lo = u16::from_le_bytes(buf[0x0C..0x0E].try_into().unwrap());
53 let free_inodes_lo = u16::from_le_bytes(buf[0x0E..0x10].try_into().unwrap());
54 let used_dirs_lo = u16::from_le_bytes(buf[0x10..0x12].try_into().unwrap());
55 let flags = u16::from_le_bytes(buf[0x12..0x14].try_into().unwrap());
56 let block_bitmap_csum_lo = u16::from_le_bytes(buf[0x18..0x1A].try_into().unwrap());
57 let inode_bitmap_csum_lo = u16::from_le_bytes(buf[0x1A..0x1C].try_into().unwrap());
58 let itable_unused_lo = u16::from_le_bytes(buf[0x1C..0x1E].try_into().unwrap());
59 let checksum = u16::from_le_bytes(buf[0x1E..0x20].try_into().unwrap());
60
61 let (
62 block_bitmap_hi,
63 inode_bitmap_hi,
64 inode_table_hi,
65 free_blocks_hi,
66 free_inodes_hi,
67 used_dirs_hi,
68 itable_unused_hi,
69 block_bitmap_csum_hi,
70 inode_bitmap_csum_hi,
71 ) = if desc_size >= 64 {
72 (
73 u32::from_le_bytes(buf[0x20..0x24].try_into().unwrap()),
74 u32::from_le_bytes(buf[0x24..0x28].try_into().unwrap()),
75 u32::from_le_bytes(buf[0x28..0x2C].try_into().unwrap()),
76 u16::from_le_bytes(buf[0x2C..0x2E].try_into().unwrap()),
77 u16::from_le_bytes(buf[0x2E..0x30].try_into().unwrap()),
78 u16::from_le_bytes(buf[0x30..0x32].try_into().unwrap()),
79 u16::from_le_bytes(buf[0x32..0x34].try_into().unwrap()),
80 u16::from_le_bytes(buf[0x38..0x3A].try_into().unwrap()),
81 u16::from_le_bytes(buf[0x3A..0x3C].try_into().unwrap()),
82 )
83 } else {
84 (0, 0, 0, 0, 0, 0, 0, 0, 0)
85 };
86
87 Ok(Self {
88 block_bitmap: ((block_bitmap_hi as u64) << 32) | block_bitmap_lo as u64,
89 inode_bitmap: ((inode_bitmap_hi as u64) << 32) | inode_bitmap_lo as u64,
90 inode_table: ((inode_table_hi as u64) << 32) | inode_table_lo as u64,
91 free_blocks_count: ((free_blocks_hi as u32) << 16) | free_blocks_lo as u32,
92 free_inodes_count: ((free_inodes_hi as u32) << 16) | free_inodes_lo as u32,
93 used_dirs_count: ((used_dirs_hi as u32) << 16) | used_dirs_lo as u32,
94 flags,
95 itable_unused: ((itable_unused_hi as u32) << 16) | itable_unused_lo as u32,
96 block_bitmap_csum: ((block_bitmap_csum_hi as u32) << 16) | block_bitmap_csum_lo as u32,
97 inode_bitmap_csum: ((inode_bitmap_csum_hi as u32) << 16) | inode_bitmap_csum_lo as u32,
98 checksum,
99 })
100 }
101
102 pub fn flags(&self) -> BgdFlags {
103 BgdFlags::from_bits_truncate(self.flags)
104 }
105}
106
107pub fn read_all<D: BlockDevice + ?Sized>(
112 dev: &D,
113 sb: &Superblock,
114 csum: &Checksummer,
115) -> Result<Vec<BlockGroupDescriptor>> {
116 let block_size = sb.block_size() as u64;
117 let bgt_block = sb.first_data_block as u64 + 1;
119 let bgt_offset = bgt_block * block_size;
120
121 let group_count = sb.block_group_count();
122 let total_bytes = group_count
132 .checked_mul(sb.desc_size as u64)
133 .ok_or(Error::Corrupt(
134 "superblock: the group descriptor table's size overflows",
135 ))?;
136 let end = bgt_offset.checked_add(total_bytes).ok_or(Error::Corrupt(
137 "superblock: the group descriptor table's extent overflows",
138 ))?;
139 if end > dev.size_bytes() {
140 return Err(Error::Corrupt(
141 "superblock: the group descriptor table reaches past the end of the device",
142 ));
143 }
144
145 let mut buf = vec![0u8; total_bytes as usize];
146 dev.read_at(bgt_offset, &mut buf)?;
147
148 let mut groups = Vec::with_capacity(group_count as usize);
149 for i in 0..(group_count as usize) {
150 let off = i * sb.desc_size as usize;
151 let raw = &buf[off..off + sb.desc_size as usize];
152 if csum.enabled && !csum.verify_bgd(i as u32, raw, sb.desc_size) {
153 return Err(Error::BadChecksum {
154 what: "block group descriptor",
155 });
156 }
157 let bgd = BlockGroupDescriptor::parse(raw, sb.desc_size)?;
158 let table_blocks = (u64::from(sb.inodes_per_group) * u64::from(sb.inode_size))
176 .div_ceil(u64::from(sb.block_size()));
177 let past_the_end = bgd.block_bitmap >= sb.blocks_count
178 || bgd.inode_bitmap >= sb.blocks_count
179 || bgd
180 .inode_table
181 .checked_add(table_blocks)
182 .is_none_or(|end| end > sb.blocks_count);
183 if past_the_end {
184 return Err(Error::Corrupt(
185 "a block group descriptor points outside the filesystem",
186 ));
187 }
188 groups.push(bgd);
189 }
190 Ok(groups)
191}
192
193pub fn locate_inode(
196 sb: &Superblock,
197 groups: &[BlockGroupDescriptor],
198 ino: u32,
199) -> Result<(u64, u32)> {
200 if ino == 0 || ino > sb.inodes_count {
201 return Err(Error::InvalidInode(ino));
202 }
203 if sb.inodes_per_group == 0 || sb.inode_size == 0 {
204 return Err(Error::Corrupt(
205 "superblock: zero inodes_per_group or inode_size",
206 ));
207 }
208 let group_idx = ((ino - 1) / sb.inodes_per_group) as usize;
209 let local_idx = ((ino - 1) % sb.inodes_per_group) as u64;
210
211 let bgd = groups.get(group_idx).ok_or(Error::InvalidInode(ino))?;
212 let block_size = sb.block_size() as u64;
213 let inode_size = sb.inode_size as u64;
214 if inode_size > block_size {
215 return Err(Error::Corrupt(
216 "superblock: inode_size larger than block_size",
217 ));
218 }
219 let inodes_per_block = block_size / inode_size;
220 if inodes_per_block == 0 {
221 return Err(Error::Corrupt("superblock: inode_size exceeds block_size"));
222 }
223
224 let block = bgd
225 .inode_table
226 .checked_add(local_idx / inodes_per_block)
227 .ok_or(Error::Corrupt("inode table block number overflow"))?;
228 let off_bytes = (local_idx % inodes_per_block) * inode_size;
229 let offset_in_block: u32 = off_bytes
230 .try_into()
231 .map_err(|_| Error::Corrupt("inode offset exceeds u32"))?;
232
233 Ok((block, offset_in_block))
234}