1use crate::block_io::BlockDevice;
7use crate::error::{Error, Result};
8
9pub const SUPERBLOCK_OFFSET: u64 = 1024;
10pub const SUPERBLOCK_SIZE: usize = 1024;
11pub const EXT4_MAGIC: u16 = 0xEF53;
12
13pub const EXT4_VALID_FS: u16 = 0x0001;
19pub const EXT4_ERROR_FS: u16 = 0x0002;
20
21#[derive(Debug, Clone)]
24pub struct Superblock {
25 pub inodes_count: u32,
26 pub blocks_count: u64, pub free_blocks_count: u64,
28 pub free_inodes_count: u32,
29 pub r_blocks_count: u64,
34 pub first_data_block: u32,
35 pub log_block_size: u32,
36 pub blocks_per_group: u32,
37 pub inodes_per_group: u32,
38 pub magic: u16,
39 pub state: u16,
43 pub errors_behavior: u16,
47 pub minor_rev_level: u16,
50 pub rev_level: u32,
51 pub inode_size: u16,
52 pub first_inode: u32,
55 pub feature_compat: u32,
56 pub feature_incompat: u32,
57 pub feature_ro_compat: u32,
58 pub uuid: [u8; 16],
59 pub volume_name: String,
60 pub last_mounted: String,
64 pub desc_size: u16, pub reserved_gdt_blocks: u16,
74 pub backup_bgs: [u32; 2],
77 pub hash_seed: [u32; 4],
78 pub default_hash_version: u8,
79 pub checksum_seed: u32, pub journal_inode: u32,
81 pub last_orphan: u32,
88 pub mtime: u32,
90 pub wtime: u32,
92 pub mnt_count: u16,
94 pub max_mnt_count: u16,
97 pub lastcheck: u32,
99 pub checkinterval: u32,
102 pub creator_os: u32,
105 pub def_resuid: u16,
107 pub def_resgid: u16,
109 pub raw: Vec<u8>, }
111
112pub(crate) fn classic_sparse_super(g: u64) -> bool {
119 fn is_power_of(mut g: u64, base: u64) -> bool {
120 while g.is_multiple_of(base) {
121 g /= base;
122 }
123 g == 1
124 }
125 g <= 1 || is_power_of(g, 3) || is_power_of(g, 5) || is_power_of(g, 7)
126}
127
128pub const MAX_LOG_BLOCK_SIZE: u32 = 6;
132
133pub const GOOD_OLD_FIRST_INODE: u32 = 11;
139
140impl Superblock {
141 pub fn read<D: BlockDevice + ?Sized>(dev: &D) -> Result<Self> {
143 let mut buf = vec![0u8; SUPERBLOCK_SIZE];
144 dev.read_at(SUPERBLOCK_OFFSET, &mut buf)?;
145 Self::parse(buf)
146 }
147
148 pub fn parse(raw: Vec<u8>) -> Result<Self> {
149 if raw.len() < SUPERBLOCK_SIZE {
150 return Err(Error::Corrupt("superblock buffer too small"));
151 }
152
153 let magic = u16::from_le_bytes([raw[0x38], raw[0x39]]);
154 if magic != EXT4_MAGIC {
155 return Err(Error::BadMagic {
156 found: magic,
157 expected: EXT4_MAGIC,
158 });
159 }
160
161 let inodes_count = u32::from_le_bytes(raw[0x00..0x04].try_into().unwrap());
162 let blocks_count_lo = u32::from_le_bytes(raw[0x04..0x08].try_into().unwrap());
163 let r_blocks_count_lo = u32::from_le_bytes(raw[0x08..0x0C].try_into().unwrap());
164 let free_blocks_count_lo = u32::from_le_bytes(raw[0x0C..0x10].try_into().unwrap());
165 let free_inodes_count = u32::from_le_bytes(raw[0x10..0x14].try_into().unwrap());
166 let first_data_block = u32::from_le_bytes(raw[0x14..0x18].try_into().unwrap());
167 let log_block_size = u32::from_le_bytes(raw[0x18..0x1C].try_into().unwrap());
168 let blocks_per_group = u32::from_le_bytes(raw[0x20..0x24].try_into().unwrap());
169 let inodes_per_group = u32::from_le_bytes(raw[0x28..0x2C].try_into().unwrap());
170 let mtime = u32::from_le_bytes(raw[0x2C..0x30].try_into().unwrap());
171 let wtime = u32::from_le_bytes(raw[0x30..0x34].try_into().unwrap());
172 let mnt_count = u16::from_le_bytes(raw[0x34..0x36].try_into().unwrap());
173 let max_mnt_count = u16::from_le_bytes(raw[0x36..0x38].try_into().unwrap());
174 let state = u16::from_le_bytes(raw[0x3A..0x3C].try_into().unwrap());
175 let errors_behavior = u16::from_le_bytes(raw[0x3C..0x3E].try_into().unwrap());
176 let minor_rev_level = u16::from_le_bytes(raw[0x3E..0x40].try_into().unwrap());
177 let lastcheck = u32::from_le_bytes(raw[0x40..0x44].try_into().unwrap());
178 let checkinterval = u32::from_le_bytes(raw[0x44..0x48].try_into().unwrap());
179 let creator_os = u32::from_le_bytes(raw[0x48..0x4C].try_into().unwrap());
180 let rev_level = u32::from_le_bytes(raw[0x4C..0x50].try_into().unwrap());
181 let def_resuid = u16::from_le_bytes(raw[0x50..0x52].try_into().unwrap());
182 let def_resgid = u16::from_le_bytes(raw[0x52..0x54].try_into().unwrap());
183
184 let first_inode = if rev_level >= 1 {
194 u32::from_le_bytes(raw[0x54..0x58].try_into().unwrap()).max(GOOD_OLD_FIRST_INODE)
195 } else {
196 GOOD_OLD_FIRST_INODE
197 };
198 let inode_size = if rev_level >= 1 {
199 u16::from_le_bytes(raw[0x58..0x5A].try_into().unwrap())
200 } else {
201 128
202 };
203 let feature_compat = if rev_level >= 1 {
204 u32::from_le_bytes(raw[0x5C..0x60].try_into().unwrap())
205 } else {
206 0
207 };
208 let feature_incompat = if rev_level >= 1 {
209 u32::from_le_bytes(raw[0x60..0x64].try_into().unwrap())
210 } else {
211 0
212 };
213 let feature_ro_compat = if rev_level >= 1 {
214 u32::from_le_bytes(raw[0x64..0x68].try_into().unwrap())
215 } else {
216 0
217 };
218
219 let reserved_gdt_blocks = if rev_level >= 1 {
222 u16::from_le_bytes(raw[0xCE..0xD0].try_into().unwrap())
223 } else {
224 0
225 };
226 let backup_bgs = if rev_level >= 1 && raw.len() >= 0x27C {
227 [
228 u32::from_le_bytes(raw[0x274..0x278].try_into().unwrap()),
229 u32::from_le_bytes(raw[0x278..0x27C].try_into().unwrap()),
230 ]
231 } else {
232 [0, 0]
233 };
234
235 let mut uuid = [0u8; 16];
236 uuid.copy_from_slice(&raw[0x68..0x78]);
237
238 let volume_name_bytes = &raw[0x78..0x88];
239 let nul = volume_name_bytes.iter().position(|&b| b == 0).unwrap_or(16);
240 let volume_name = String::from_utf8_lossy(&volume_name_bytes[..nul]).into_owned();
241
242 let last_mounted_bytes = &raw[0x88..0xC8];
246 let nul = last_mounted_bytes
247 .iter()
248 .position(|&b| b == 0)
249 .unwrap_or(64);
250 let last_mounted = String::from_utf8_lossy(&last_mounted_bytes[..nul]).into_owned();
251
252 let desc_size = u16::from_le_bytes(raw[0xFE..0x100].try_into().unwrap());
253 let desc_size = if desc_size == 0 { 32 } else { desc_size };
255
256 let mut hash_seed = [0u32; 4];
257 for (i, slot) in hash_seed.iter_mut().enumerate() {
258 let off = 0xEC + i * 4;
259 *slot = u32::from_le_bytes(raw[off..off + 4].try_into().unwrap());
260 }
261 let default_hash_version = raw[0xFC];
262
263 let blocks_count_hi = u32::from_le_bytes(raw[0x150..0x154].try_into().unwrap());
267 let r_blocks_count_hi = u32::from_le_bytes(raw[0x154..0x158].try_into().unwrap());
268 let free_blocks_count_hi = u32::from_le_bytes(raw[0x158..0x15C].try_into().unwrap());
269
270 let blocks_count = ((blocks_count_hi as u64) << 32) | (blocks_count_lo as u64);
271 let r_blocks_count = ((r_blocks_count_hi as u64) << 32) | (r_blocks_count_lo as u64);
272 let free_blocks_count =
273 ((free_blocks_count_hi as u64) << 32) | (free_blocks_count_lo as u64);
274
275 let checksum_seed = u32::from_le_bytes(raw[0x270..0x274].try_into().unwrap());
276 let journal_inode = u32::from_le_bytes(raw[0xE0..0xE4].try_into().unwrap());
277 let last_orphan = u32::from_le_bytes(raw[0xE8..0xEC].try_into().unwrap());
278
279 if blocks_per_group == 0 {
283 return Err(Error::Corrupt("superblock: blocks_per_group == 0"));
284 }
285 if inodes_per_group == 0 {
286 return Err(Error::Corrupt("superblock: inodes_per_group == 0"));
287 }
288 if inode_size == 0 {
289 return Err(Error::Corrupt("superblock: inode_size == 0"));
290 }
291 if log_block_size > MAX_LOG_BLOCK_SIZE {
302 return Err(Error::Corrupt(
303 "superblock: log_block_size exceeds the largest ext4 block",
304 ));
305 }
306 let sixty_four_bit = feature_incompat & crate::features::Incompat::BIT64.bits() != 0;
313 let smallest = if sixty_four_bit { 64 } else { 32 };
314 if desc_size < smallest || !desc_size.is_power_of_two() {
315 return Err(Error::Corrupt(
316 "superblock: desc_size is not a group descriptor size",
317 ));
318 }
319 if blocks_count == 0 {
320 return Err(Error::Corrupt("superblock: blocks_count == 0"));
321 }
322
323 Ok(Self {
324 inodes_count,
325 blocks_count,
326 free_blocks_count,
327 free_inodes_count,
328 r_blocks_count,
329 first_data_block,
330 log_block_size,
331 blocks_per_group,
332 inodes_per_group,
333 magic,
334 state,
335 errors_behavior,
336 minor_rev_level,
337 rev_level,
338 inode_size,
339 first_inode,
340 feature_compat,
341 feature_incompat,
342 feature_ro_compat,
343 uuid,
344 volume_name,
345 last_mounted,
346 desc_size,
347 reserved_gdt_blocks,
348 backup_bgs,
349 hash_seed,
350 default_hash_version,
351 checksum_seed,
352 journal_inode,
353 last_orphan,
354 mtime,
355 wtime,
356 mnt_count,
357 max_mnt_count,
358 lastcheck,
359 checkinterval,
360 creator_os,
361 def_resuid,
362 def_resgid,
363 raw,
364 })
365 }
366
367 pub fn is_clean(&self) -> bool {
374 self.state & EXT4_VALID_FS != 0
375 }
376
377 pub fn group_has_super(&self, g: u64) -> bool {
396 use crate::features::{Compat, RoCompat};
397
398 if g == 0 {
399 return true;
400 }
401 if self.feature_compat & Compat::SPARSE_SUPER2.bits() != 0 {
402 return self.backup_bgs.iter().any(|&b| u64::from(b) == g);
403 }
404 if self.feature_ro_compat & RoCompat::SPARSE_SUPER.bits() == 0 {
405 return true;
406 }
407 classic_sparse_super(g)
408 }
409
410 pub fn block_size(&self) -> u32 {
412 1024u32 << self.log_block_size
413 }
414
415 pub fn block_group_count(&self) -> u64 {
417 self.blocks_count.div_ceil(self.blocks_per_group as u64)
418 }
419
420 pub fn is_64bit(&self) -> bool {
422 self.feature_incompat & crate::features::Incompat::BIT64.bits() != 0
423 }
424}
425
426#[cfg(test)]
427mod backup_layout_tests {
428 use super::*;
429 use crate::features::{Compat, RoCompat};
430
431 fn sb_with(compat: u32, ro_compat: u32, backup_bgs: [u32; 2], reserved_gdt: u16) -> Superblock {
433 let mut raw = vec![0u8; SUPERBLOCK_SIZE];
434 raw[0x38..0x3A].copy_from_slice(&EXT4_MAGIC.to_le_bytes());
435 raw[0x00..0x04].copy_from_slice(&8192u32.to_le_bytes()); raw[0x04..0x08].copy_from_slice(&65536u32.to_le_bytes()); raw[0x14..0x18].copy_from_slice(&1u32.to_le_bytes()); raw[0x20..0x24].copy_from_slice(&8192u32.to_le_bytes()); raw[0x28..0x2C].copy_from_slice(&2048u32.to_le_bytes()); raw[0x4C..0x50].copy_from_slice(&1u32.to_le_bytes()); raw[0x58..0x5A].copy_from_slice(&256u16.to_le_bytes()); raw[0x5C..0x60].copy_from_slice(&compat.to_le_bytes());
443 raw[0x64..0x68].copy_from_slice(&ro_compat.to_le_bytes());
444 raw[0xCE..0xD0].copy_from_slice(&reserved_gdt.to_le_bytes());
445 raw[0xFE..0x100].copy_from_slice(&64u16.to_le_bytes()); raw[0x274..0x278].copy_from_slice(&backup_bgs[0].to_le_bytes());
447 raw[0x278..0x27C].copy_from_slice(&backup_bgs[1].to_le_bytes());
448 Superblock::parse(raw).expect("superblock")
449 }
450
451 #[test]
454 fn the_classic_sparse_layout() {
455 let sb = sb_with(0, RoCompat::SPARSE_SUPER.bits(), [0, 0], 0);
456 for g in [0, 1, 3, 5, 7, 9, 25, 27, 49, 81, 125] {
457 assert!(sb.group_has_super(g), "group {g} should carry a backup");
458 }
459 for g in [2, 4, 6, 8, 10, 11, 26, 50, 100] {
460 assert!(!sb.group_has_super(g), "group {g} should not");
461 }
462 }
463
464 #[test]
471 fn without_sparse_super_every_group_carries_a_backup() {
472 let sb = sb_with(0, 0, [0, 0], 0);
473 for g in 0..40 {
474 assert!(sb.group_has_super(g), "group {g} should carry a backup");
475 }
476 }
477
478 #[test]
481 fn sparse_super2_names_its_own_groups() {
482 let sb = sb_with(
483 Compat::SPARSE_SUPER2.bits(),
484 RoCompat::SPARSE_SUPER.bits(),
485 [4, 17],
486 0,
487 );
488 assert!(sb.group_has_super(0), "group 0 always carries one");
489 assert!(sb.group_has_super(4));
490 assert!(sb.group_has_super(17));
491 for g in [1, 3, 5, 7, 9, 25, 49] {
495 assert!(
496 !sb.group_has_super(g),
497 "group {g} is not named by s_backup_bgs and must not be treated as \
498 carrying a backup"
499 );
500 }
501 }
502
503 #[test]
505 fn reserved_gdt_blocks_is_read() {
506 assert_eq!(
507 sb_with(0, RoCompat::SPARSE_SUPER.bits(), [0, 0], 1024).reserved_gdt_blocks,
508 1024
509 );
510 assert_eq!(
511 sb_with(0, RoCompat::SPARSE_SUPER.bits(), [0, 0], 0).reserved_gdt_blocks,
512 0
513 );
514 }
515}