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)]
25pub struct Superblock {
26 pub inodes_count: u32,
27 pub blocks_count: u64, pub free_blocks_count: u64,
29 pub free_inodes_count: u32,
30 pub r_blocks_count: u64,
35 pub first_data_block: u32,
36 pub log_block_size: u32,
37 pub blocks_per_group: u32,
38 pub inodes_per_group: u32,
39 pub magic: u16,
40 pub state: u16,
44 pub errors_behavior: u16,
48 pub minor_rev_level: u16,
51 pub rev_level: u32,
52 pub inode_size: u16,
53 pub first_inode: u32,
56 pub feature_compat: u32,
57 pub feature_incompat: u32,
58 pub feature_ro_compat: u32,
59 pub uuid: [u8; 16],
60 pub volume_name: String,
61 pub last_mounted: String,
65 pub desc_size: u16, pub reserved_gdt_blocks: u16,
75 pub backup_bgs: [u32; 2],
78 pub hash_seed: [u32; 4],
79 pub default_hash_version: u8,
80 pub checksum_seed: u32, pub journal_inode: u32,
82 pub last_orphan: u32,
89 pub mtime: u32,
91 pub wtime: u32,
93 pub mnt_count: u16,
95 pub max_mnt_count: u16,
98 pub lastcheck: u32,
100 pub checkinterval: u32,
103 pub creator_os: u32,
106 pub def_resuid: u16,
108 pub def_resgid: u16,
110 pub raw: Vec<u8>, }
112
113pub(crate) fn classic_sparse_super(g: u64) -> bool {
120 fn is_power_of(mut g: u64, base: u64) -> bool {
121 while g.is_multiple_of(base) {
122 g /= base;
123 }
124 g == 1
125 }
126 g <= 1 || is_power_of(g, 3) || is_power_of(g, 5) || is_power_of(g, 7)
127}
128
129pub const MAX_LOG_BLOCK_SIZE: u32 = 6;
133
134pub const GOOD_OLD_FIRST_INODE: u32 = 11;
140
141impl Superblock {
142 pub fn read<D: BlockDevice + ?Sized>(dev: &D) -> Result<Self> {
144 let mut buf = vec![0u8; SUPERBLOCK_SIZE];
145 dev.read_at(SUPERBLOCK_OFFSET, &mut buf)?;
146 Self::parse(buf)
147 }
148
149 pub fn parse(raw: Vec<u8>) -> Result<Self> {
150 if raw.len() < SUPERBLOCK_SIZE {
151 return Err(Error::Corrupt("superblock buffer too small"));
152 }
153
154 let magic = u16::from_le_bytes([raw[0x38], raw[0x39]]);
155 if magic != EXT4_MAGIC {
156 return Err(Error::BadMagic {
157 found: magic,
158 expected: EXT4_MAGIC,
159 });
160 }
161
162 let inodes_count = u32::from_le_bytes(raw[0x00..0x04].try_into().unwrap());
163 let blocks_count_lo = u32::from_le_bytes(raw[0x04..0x08].try_into().unwrap());
164 let r_blocks_count_lo = u32::from_le_bytes(raw[0x08..0x0C].try_into().unwrap());
165 let free_blocks_count_lo = u32::from_le_bytes(raw[0x0C..0x10].try_into().unwrap());
166 let free_inodes_count = u32::from_le_bytes(raw[0x10..0x14].try_into().unwrap());
167 let first_data_block = u32::from_le_bytes(raw[0x14..0x18].try_into().unwrap());
168 let log_block_size = u32::from_le_bytes(raw[0x18..0x1C].try_into().unwrap());
169 let blocks_per_group = u32::from_le_bytes(raw[0x20..0x24].try_into().unwrap());
170 let inodes_per_group = u32::from_le_bytes(raw[0x28..0x2C].try_into().unwrap());
171 let mtime = u32::from_le_bytes(raw[0x2C..0x30].try_into().unwrap());
172 let wtime = u32::from_le_bytes(raw[0x30..0x34].try_into().unwrap());
173 let mnt_count = u16::from_le_bytes(raw[0x34..0x36].try_into().unwrap());
174 let max_mnt_count = u16::from_le_bytes(raw[0x36..0x38].try_into().unwrap());
175 let state = u16::from_le_bytes(raw[0x3A..0x3C].try_into().unwrap());
176 let errors_behavior = u16::from_le_bytes(raw[0x3C..0x3E].try_into().unwrap());
177 let minor_rev_level = u16::from_le_bytes(raw[0x3E..0x40].try_into().unwrap());
178 let lastcheck = u32::from_le_bytes(raw[0x40..0x44].try_into().unwrap());
179 let checkinterval = u32::from_le_bytes(raw[0x44..0x48].try_into().unwrap());
180 let creator_os = u32::from_le_bytes(raw[0x48..0x4C].try_into().unwrap());
181 let rev_level = u32::from_le_bytes(raw[0x4C..0x50].try_into().unwrap());
182 let def_resuid = u16::from_le_bytes(raw[0x50..0x52].try_into().unwrap());
183 let def_resgid = u16::from_le_bytes(raw[0x52..0x54].try_into().unwrap());
184
185 let first_inode = if rev_level >= 1 {
195 u32::from_le_bytes(raw[0x54..0x58].try_into().unwrap()).max(GOOD_OLD_FIRST_INODE)
196 } else {
197 GOOD_OLD_FIRST_INODE
198 };
199 let inode_size = if rev_level >= 1 {
200 u16::from_le_bytes(raw[0x58..0x5A].try_into().unwrap())
201 } else {
202 128
203 };
204 let feature_compat = if rev_level >= 1 {
205 u32::from_le_bytes(raw[0x5C..0x60].try_into().unwrap())
206 } else {
207 0
208 };
209 let feature_incompat = if rev_level >= 1 {
210 u32::from_le_bytes(raw[0x60..0x64].try_into().unwrap())
211 } else {
212 0
213 };
214 let feature_ro_compat = if rev_level >= 1 {
215 u32::from_le_bytes(raw[0x64..0x68].try_into().unwrap())
216 } else {
217 0
218 };
219
220 let reserved_gdt_blocks = if rev_level >= 1 {
224 u16::from_le_bytes(raw[0xCE..0xD0].try_into().unwrap())
225 } else {
226 0
227 };
228 let backup_bgs = if rev_level >= 1 && raw.len() >= 0x254 {
229 [
230 u32::from_le_bytes(raw[0x24C..0x250].try_into().unwrap()),
231 u32::from_le_bytes(raw[0x250..0x254].try_into().unwrap()),
232 ]
233 } else {
234 [0, 0]
235 };
236
237 let mut uuid = [0u8; 16];
238 uuid.copy_from_slice(&raw[0x68..0x78]);
239
240 let volume_name_bytes = &raw[0x78..0x88];
241 let nul = volume_name_bytes.iter().position(|&b| b == 0).unwrap_or(16);
242 let volume_name = String::from_utf8_lossy(&volume_name_bytes[..nul]).into_owned();
243
244 let last_mounted_bytes = &raw[0x88..0xC8];
248 let nul = last_mounted_bytes
249 .iter()
250 .position(|&b| b == 0)
251 .unwrap_or(64);
252 let last_mounted = String::from_utf8_lossy(&last_mounted_bytes[..nul]).into_owned();
253
254 let desc_size = u16::from_le_bytes(raw[0xFE..0x100].try_into().unwrap());
255 let desc_size = if desc_size == 0 { 32 } else { desc_size };
257
258 let mut hash_seed = [0u32; 4];
259 for (i, slot) in hash_seed.iter_mut().enumerate() {
260 let off = 0xEC + i * 4;
261 *slot = u32::from_le_bytes(raw[off..off + 4].try_into().unwrap());
262 }
263 let default_hash_version = raw[0xFC];
264
265 let blocks_count_hi = u32::from_le_bytes(raw[0x150..0x154].try_into().unwrap());
269 let r_blocks_count_hi = u32::from_le_bytes(raw[0x154..0x158].try_into().unwrap());
270 let free_blocks_count_hi = u32::from_le_bytes(raw[0x158..0x15C].try_into().unwrap());
271
272 let blocks_count = ((blocks_count_hi as u64) << 32) | (blocks_count_lo as u64);
273 let r_blocks_count = ((r_blocks_count_hi as u64) << 32) | (r_blocks_count_lo as u64);
274 let free_blocks_count =
275 ((free_blocks_count_hi as u64) << 32) | (free_blocks_count_lo as u64);
276
277 let checksum_seed = u32::from_le_bytes(raw[0x270..0x274].try_into().unwrap());
278 let journal_inode = u32::from_le_bytes(raw[0xE0..0xE4].try_into().unwrap());
279 let last_orphan = u32::from_le_bytes(raw[0xE8..0xEC].try_into().unwrap());
280
281 if blocks_per_group == 0 {
285 return Err(Error::Corrupt("superblock: blocks_per_group == 0"));
286 }
287 if inodes_per_group == 0 {
288 return Err(Error::Corrupt("superblock: inodes_per_group == 0"));
289 }
290 if inode_size == 0 {
291 return Err(Error::Corrupt("superblock: inode_size == 0"));
292 }
293 if log_block_size > MAX_LOG_BLOCK_SIZE {
304 return Err(Error::Corrupt(
305 "superblock: log_block_size exceeds the largest ext4 block",
306 ));
307 }
308 check_group_fits_bitmap(
309 raw.as_slice(),
310 log_block_size,
311 blocks_per_group,
312 feature_ro_compat & crate::features::RoCompat::BIGALLOC.bits() != 0,
313 )?;
314 let sixty_four_bit = feature_incompat & crate::features::Incompat::BIT64.bits() != 0;
321 let desc_size = if sixty_four_bit {
331 if !(64..=1024).contains(&desc_size) || !desc_size.is_power_of_two() {
332 return Err(Error::Corrupt(
333 "superblock: desc_size is not a group descriptor size",
334 ));
335 }
336 desc_size
337 } else {
338 32
339 };
340 if blocks_count == 0 {
341 return Err(Error::Corrupt("superblock: blocks_count == 0"));
342 }
343 if u64::from(first_data_block) >= blocks_count {
351 return Err(Error::Corrupt(
352 "superblock: first_data_block is at or past the end of the filesystem",
353 ));
354 }
355 if first_data_block == 0
359 && log_block_size == 0
360 && feature_ro_compat & crate::features::RoCompat::BIGALLOC.bits() == 0
361 {
362 return Err(Error::Corrupt(
363 "superblock: a 1 KiB-block filesystem's first data block is 0, not 1",
364 ));
365 }
366
367 Ok(Self {
368 inodes_count,
369 blocks_count,
370 free_blocks_count,
371 free_inodes_count,
372 r_blocks_count,
373 first_data_block,
374 log_block_size,
375 blocks_per_group,
376 inodes_per_group,
377 magic,
378 state,
379 errors_behavior,
380 minor_rev_level,
381 rev_level,
382 inode_size,
383 first_inode,
384 feature_compat,
385 feature_incompat,
386 feature_ro_compat,
387 uuid,
388 volume_name,
389 last_mounted,
390 desc_size,
391 reserved_gdt_blocks,
392 backup_bgs,
393 hash_seed,
394 default_hash_version,
395 checksum_seed,
396 journal_inode,
397 last_orphan,
398 mtime,
399 wtime,
400 mnt_count,
401 max_mnt_count,
402 lastcheck,
403 checkinterval,
404 creator_os,
405 def_resuid,
406 def_resgid,
407 raw,
408 })
409 }
410
411 pub fn is_clean(&self) -> bool {
418 self.state & EXT4_VALID_FS != 0
419 }
420
421 pub fn group_has_super(&self, g: u64) -> bool {
440 use crate::features::{Compat, RoCompat};
441
442 if g == 0 {
443 return true;
444 }
445 if self.feature_compat & Compat::SPARSE_SUPER2.bits() != 0 {
446 return self.backup_bgs.iter().any(|&b| u64::from(b) == g);
447 }
448 if self.feature_ro_compat & RoCompat::SPARSE_SUPER.bits() == 0 {
449 return true;
450 }
451 classic_sparse_super(g)
452 }
453
454 pub fn descs_per_block(&self) -> u64 {
456 u64::from(self.block_size()) / u64::from(self.desc_size.max(1))
457 }
458
459 pub fn first_meta_bg(&self) -> u64 {
463 if self.feature_incompat & crate::features::Incompat::META_BG.bits() == 0 {
464 return 0;
465 }
466 self.raw
467 .get(0x104..0x108)
468 .map_or(0, |f| u64::from(u32::from_le_bytes(f.try_into().unwrap())))
469 }
470
471 fn primary_superblock_block(&self) -> u64 {
515 SUPERBLOCK_OFFSET / u64::from(self.block_size())
516 }
517
518 fn first_block_of_group(&self, group: u64) -> u64 {
520 u64::from(self.first_data_block) + group * u64::from(self.blocks_per_group)
521 }
522
523 fn meta_group_holding(&self, group: u64) -> Option<u64> {
526 if self.feature_incompat & crate::features::Incompat::META_BG.bits() == 0 {
527 return None;
528 }
529 let meta_group = group / self.descs_per_block();
530 (meta_group >= self.first_meta_bg()).then_some(meta_group)
531 }
532
533 fn shared_descriptor_run(&self) -> u64 {
535 if self.feature_incompat & crate::features::Incompat::META_BG.bits() != 0 {
536 self.first_meta_bg()
537 } else {
538 self.block_group_count().div_ceil(self.descs_per_block())
539 }
540 }
541
542 pub fn descriptor_location(&self, group: u64) -> (u64, usize) {
545 let per_block = self.descs_per_block();
546 let offset = ((group % per_block) * u64::from(self.desc_size)) as usize;
547 let block = match self.meta_group_holding(group) {
548 None => self.primary_superblock_block() + 1 + group / per_block,
553 Some(meta_group) => {
554 let head_group = meta_group * per_block;
555 if head_group == 0 {
556 self.primary_superblock_block() + 1
557 } else {
558 self.first_block_of_group(head_group)
559 + u64::from(self.group_has_super(head_group))
560 }
561 }
562 };
563 (block, offset)
564 }
565
566 pub fn group_head_metadata_blocks(&self, group: u64) -> u64 {
570 let superblock = u64::from(self.group_has_super(group));
571 match self.meta_group_holding(group) {
572 Some(meta_group) => {
573 let per_block = self.descs_per_block();
574 let place = group - meta_group * per_block;
575 let holds_descriptor_copy = place == 0 || place == 1 || place == per_block - 1;
576 superblock + u64::from(holds_descriptor_copy)
577 }
578 None if superblock == 1 => {
579 1 + self.shared_descriptor_run() + u64::from(self.reserved_gdt_blocks)
580 }
581 None => 0,
582 }
583 }
584
585 pub fn unsigned_hash(&self) -> bool {
589 self.raw
590 .get(0x160..0x164)
591 .is_some_and(|f| u32::from_le_bytes(f.try_into().unwrap()) & 0x2 != 0)
592 }
593
594 pub fn block_size(&self) -> u32 {
596 1024u32 << self.log_block_size
597 }
598
599 pub fn filesystem_bytes(&self) -> Option<u64> {
603 self.blocks_count.checked_mul(u64::from(self.block_size()))
604 }
605
606 pub fn check_fits_device(&self, device_bytes: u64) -> Result<()> {
613 match self.filesystem_bytes() {
614 Some(bytes) if bytes <= device_bytes => Ok(()),
615 _ => Err(Error::Corrupt(
616 "superblock: blocks_count makes the filesystem larger than the device",
617 )),
618 }
619 }
620
621 pub fn block_group_count(&self) -> u64 {
633 group_count(
634 self.blocks_count,
635 self.first_data_block,
636 self.blocks_per_group,
637 )
638 }
639
640 pub fn is_64bit(&self) -> bool {
642 self.feature_incompat & crate::features::Incompat::BIT64.bits() != 0
643 }
644}
645
646#[cfg(test)]
647mod backup_layout_tests {
648 use super::*;
649 use crate::features::{Compat, RoCompat};
650
651 fn sb_with(compat: u32, ro_compat: u32, backup_bgs: [u32; 2], reserved_gdt: u16) -> Superblock {
653 let mut raw = vec![0u8; SUPERBLOCK_SIZE];
654 raw[0x38..0x3A].copy_from_slice(&EXT4_MAGIC.to_le_bytes());
655 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());
663 raw[0x64..0x68].copy_from_slice(&ro_compat.to_le_bytes());
664 raw[0xCE..0xD0].copy_from_slice(&reserved_gdt.to_le_bytes());
665 raw[0xFE..0x100].copy_from_slice(&64u16.to_le_bytes()); raw[0x24C..0x250].copy_from_slice(&backup_bgs[0].to_le_bytes());
667 raw[0x250..0x254].copy_from_slice(&backup_bgs[1].to_le_bytes());
668 Superblock::parse(raw).expect("superblock")
669 }
670
671 #[test]
674 fn the_classic_sparse_layout() {
675 let sb = sb_with(0, RoCompat::SPARSE_SUPER.bits(), [0, 0], 0);
676 for g in [0, 1, 3, 5, 7, 9, 25, 27, 49, 81, 125] {
677 assert!(sb.group_has_super(g), "group {g} should carry a backup");
678 }
679 for g in [2, 4, 6, 8, 10, 11, 26, 50, 100] {
680 assert!(!sb.group_has_super(g), "group {g} should not");
681 }
682 }
683
684 #[test]
691 fn without_sparse_super_every_group_carries_a_backup() {
692 let sb = sb_with(0, 0, [0, 0], 0);
693 for g in 0..40 {
694 assert!(sb.group_has_super(g), "group {g} should carry a backup");
695 }
696 }
697
698 #[test]
701 fn sparse_super2_names_its_own_groups() {
702 let sb = sb_with(
703 Compat::SPARSE_SUPER2.bits(),
704 RoCompat::SPARSE_SUPER.bits(),
705 [4, 17],
706 0,
707 );
708 assert!(sb.group_has_super(0), "group 0 always carries one");
709 assert!(sb.group_has_super(4));
710 assert!(sb.group_has_super(17));
711 for g in [1, 3, 5, 7, 9, 25, 49] {
715 assert!(
716 !sb.group_has_super(g),
717 "group {g} is not named by s_backup_bgs and must not be treated as \
718 carrying a backup"
719 );
720 }
721 }
722
723 #[test]
725 fn reserved_gdt_blocks_is_read() {
726 assert_eq!(
727 sb_with(0, RoCompat::SPARSE_SUPER.bits(), [0, 0], 1024).reserved_gdt_blocks,
728 1024
729 );
730 assert_eq!(
731 sb_with(0, RoCompat::SPARSE_SUPER.bits(), [0, 0], 0).reserved_gdt_blocks,
732 0
733 );
734 }
735}
736
737#[cfg(test)]
743mod descriptor_layout_tests {
744 use super::*;
745 use crate::features::{Incompat, RoCompat};
746
747 fn sb(first_data_block: u32, meta_bg_from: Option<u32>, reserved_gdt: u16) -> Superblock {
750 let mut raw = vec![0u8; SUPERBLOCK_SIZE];
751 raw[0x38..0x3A].copy_from_slice(&EXT4_MAGIC.to_le_bytes());
752 raw[0x00..0x04].copy_from_slice(&25600u32.to_le_bytes()); raw[0x04..0x08].copy_from_slice(&(102_400 + first_data_block).to_le_bytes());
754 raw[0x14..0x18].copy_from_slice(&first_data_block.to_le_bytes());
755 raw[0x20..0x24].copy_from_slice(&1024u32.to_le_bytes()); raw[0x24..0x28].copy_from_slice(&1024u32.to_le_bytes()); raw[0x28..0x2C].copy_from_slice(&256u32.to_le_bytes()); raw[0x4C..0x50].copy_from_slice(&1u32.to_le_bytes()); raw[0x58..0x5A].copy_from_slice(&256u16.to_le_bytes()); raw[0x64..0x68].copy_from_slice(&RoCompat::SPARSE_SUPER.bits().to_le_bytes());
761 let mut incompat = Incompat::BIT64.bits();
762 if let Some(first) = meta_bg_from {
763 incompat |= Incompat::META_BG.bits();
764 raw[0x104..0x108].copy_from_slice(&first.to_le_bytes());
765 }
766 raw[0x60..0x64].copy_from_slice(&incompat.to_le_bytes());
767 raw[0xCE..0xD0].copy_from_slice(&reserved_gdt.to_le_bytes());
768 raw[0xFE..0x100].copy_from_slice(&64u16.to_le_bytes()); Superblock::parse(raw).expect("superblock")
770 }
771
772 #[test]
773 fn without_meta_bg_the_table_follows_the_superblock() {
774 let sb = sb(1, None, 50);
775 assert_eq!(sb.descriptor_location(0), (2, 0));
776 assert_eq!(sb.descriptor_location(17), (3, 64));
777 assert_eq!(sb.descriptor_location(99), (8, 3 * 64));
778 assert_eq!(sb.group_head_metadata_blocks(0), 1 + 7 + 50);
780 assert_eq!(sb.group_head_metadata_blocks(1), 1 + 7 + 50);
781 assert_eq!(sb.group_head_metadata_blocks(2), 0);
782 assert_eq!(sb.group_head_metadata_blocks(49), 1 + 7 + 50);
783 }
784
785 #[test]
789 fn meta_groups_before_first_meta_bg_keep_the_shared_run() {
790 let sb = sb(1, Some(2), 0);
791 assert_eq!(sb.descriptor_location(5), (2, 5 * 64));
792 assert_eq!(sb.descriptor_location(31), (3, 15 * 64));
793 assert_eq!(sb.descriptor_location(32), (1 + 32 * 1024, 0));
795 assert_eq!(sb.descriptor_location(47), (1 + 32 * 1024, 15 * 64));
796 assert_eq!(sb.descriptor_location(50), (1 + 48 * 1024, 2 * 64));
799 assert_eq!(sb.group_head_metadata_blocks(0), 1 + 2);
800 assert_eq!(sb.group_head_metadata_blocks(1), 1 + 2);
801 assert_eq!(sb.group_head_metadata_blocks(3), 1 + 2);
802 assert_eq!(sb.group_head_metadata_blocks(32), 1);
803 assert_eq!(sb.group_head_metadata_blocks(33), 1);
804 assert_eq!(sb.group_head_metadata_blocks(34), 0);
805 assert_eq!(sb.group_head_metadata_blocks(47), 1);
806 assert_eq!(sb.group_head_metadata_blocks(49), 1 + 1, "backup and copy");
807 assert_eq!(
808 sb.group_head_metadata_blocks(27),
809 1 + 2,
810 "3^3, before first_meta_bg"
811 );
812 assert_eq!(
813 sb.group_head_metadata_blocks(81),
814 1 + 1,
815 "3^4, second of its meta group"
816 );
817 }
818}
819
820#[cfg(test)]
821mod geometry_tests {
822 use super::*;
823
824 fn raw(blocks_count: u32, first_data_block: u32, log_block_size: u32) -> Vec<u8> {
825 let mut raw = vec![0u8; SUPERBLOCK_SIZE];
826 raw[0x38..0x3A].copy_from_slice(&EXT4_MAGIC.to_le_bytes());
827 raw[0x00..0x04].copy_from_slice(&8192u32.to_le_bytes()); raw[0x04..0x08].copy_from_slice(&blocks_count.to_le_bytes());
829 raw[0x14..0x18].copy_from_slice(&first_data_block.to_le_bytes());
830 raw[0x18..0x1C].copy_from_slice(&log_block_size.to_le_bytes());
831 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[0xFE..0x100].copy_from_slice(&64u16.to_le_bytes()); raw
837 }
838
839 fn refusal(raw: Vec<u8>) -> String {
840 match Superblock::parse(raw) {
841 Err(Error::Corrupt(m)) => m.to_string(),
842 other => panic!("expected Corrupt, got {:?}", other.map(|_| ())),
843 }
844 }
845
846 #[test]
849 fn a_first_data_block_past_the_end_is_refused() {
850 for (blocks, first) in [(16384u32, 16384u32), (16384, 20000), (100, u32::MAX)] {
851 assert!(
852 refusal(raw(blocks, first, 0)).contains("first_data_block"),
853 "blocks {blocks}, first {first}"
854 );
855 }
856 }
857
858 #[test]
862 fn a_one_kib_filesystem_starting_at_block_zero_is_refused() {
863 assert!(refusal(raw(16384, 0, 0)).contains("first data block is 0"));
864
865 let mut bigalloc = raw(16384, 0, 0);
866 bigalloc[0x64..0x68]
867 .copy_from_slice(&crate::features::RoCompat::BIGALLOC.bits().to_le_bytes());
868 bigalloc[0x24..0x28].copy_from_slice(&8192u32.to_le_bytes()); Superblock::parse(bigalloc)
870 .expect("a 1 KiB bigalloc filesystem may start its groups at block 0");
871 }
872
873 #[test]
878 fn more_blocks_per_group_than_a_bitmap_has_bits_is_refused() {
879 for (log, bpg) in [(0u32, 8193u32), (2, 32769), (2, u32::MAX)] {
880 let mut r = raw(65536, u32::from(log == 0), log);
881 r[0x20..0x24].copy_from_slice(&bpg.to_le_bytes());
882 assert!(
883 refusal(r).contains("blocks_per_group"),
884 "log {log}, blocks_per_group {bpg}"
885 );
886 }
887 for (log, bpg) in [(0u32, 8192u32), (2, 32768)] {
888 let mut r = raw(65536, u32::from(log == 0), log);
889 r[0x20..0x24].copy_from_slice(&bpg.to_le_bytes());
890 Superblock::parse(r)
891 .unwrap_or_else(|e| panic!("log {log}, blocks_per_group {bpg}: {e:?}"));
892 }
893 }
894
895 #[test]
899 fn a_bigalloc_group_is_bounded_in_clusters() {
900 let bigalloc = |log_cluster: u32, cpg: u32, bpg: u32| {
901 let mut r = raw(1 << 20, 0, 2);
902 r[0x64..0x68]
903 .copy_from_slice(&crate::features::RoCompat::BIGALLOC.bits().to_le_bytes());
904 r[0x1C..0x20].copy_from_slice(&log_cluster.to_le_bytes());
905 r[0x20..0x24].copy_from_slice(&bpg.to_le_bytes());
906 r[0x24..0x28].copy_from_slice(&cpg.to_le_bytes());
907 r
908 };
909 Superblock::parse(bigalloc(6, 32768, 32768 * 16)).expect("mke2fs -O bigalloc -C 65536");
912 assert!(refusal(bigalloc(6, 32769, 32769 * 16)).contains("clusters_per_group"));
913 assert!(refusal(bigalloc(6, 32768, 32768)).contains("clusters_per_group"));
914 assert!(refusal(bigalloc(6, 0, 32768)).contains("clusters_per_group"));
915 assert!(refusal(bigalloc(1, 32768, 32768)).contains("cluster"));
916 assert!(refusal(bigalloc(40, 1, 1)).contains("cluster"));
917 }
918
919 #[test]
922 fn the_geometries_mke2fs_writes_still_parse() {
923 for (blocks, first, log) in [
924 (16385u32, 1u32, 0u32),
925 (16384, 1, 0),
926 (65536, 0, 2),
927 (1, 0, 2),
928 ] {
929 Superblock::parse(raw(blocks, first, log))
930 .unwrap_or_else(|e| panic!("blocks {blocks}, first {first}, log {log}: {e:?}"));
931 }
932 }
933}
934
935const MAX_LOG_CLUSTER_SIZE: u32 = 20;
939
940fn check_group_fits_bitmap(
951 raw: &[u8],
952 log_block_size: u32,
953 blocks_per_group: u32,
954 bigalloc: bool,
955) -> Result<()> {
956 let bitmap_bits = 8u64 << (10 + log_block_size);
957 if !bigalloc {
958 if u64::from(blocks_per_group) > bitmap_bits {
959 return Err(Error::Corrupt(
960 "superblock: blocks_per_group exceeds the bits in one bitmap block",
961 ));
962 }
963 return Ok(());
964 }
965 let log_cluster_size = u32::from_le_bytes(raw[0x1C..0x20].try_into().unwrap());
966 if log_cluster_size < log_block_size || log_cluster_size > MAX_LOG_CLUSTER_SIZE {
967 return Err(Error::Corrupt(
968 "superblock: bigalloc cluster is smaller than a block or larger than ext4 allows",
969 ));
970 }
971 let clusters_per_group = u32::from_le_bytes(raw[0x24..0x28].try_into().unwrap());
972 if clusters_per_group == 0 || u64::from(clusters_per_group) > bitmap_bits {
973 return Err(Error::Corrupt(
974 "superblock: clusters_per_group is zero or exceeds the bits in one bitmap block",
975 ));
976 }
977 let cluster_ratio_log = log_cluster_size - log_block_size;
978 if u64::from(clusters_per_group) << cluster_ratio_log != u64::from(blocks_per_group) {
979 return Err(Error::Corrupt(
980 "superblock: blocks_per_group is not clusters_per_group clusters",
981 ));
982 }
983 Ok(())
984}
985
986pub fn group_count(blocks_count: u64, first_data_block: u32, blocks_per_group: u32) -> u64 {
989 blocks_count
990 .saturating_sub(u64::from(first_data_block))
991 .div_ceil(u64::from(blocks_per_group).max(1))
992}
993
994#[cfg(test)]
995mod group_count_tests {
996 use super::group_count;
997
998 #[test]
1002 fn the_group_count_starts_at_the_first_data_block() {
1003 for (blocks, first, bpg, groups) in [
1004 (16385u64, 1u32, 8192u32, 2u64),
1005 (16384, 1, 8192, 2),
1006 (24577, 1, 8192, 3),
1007 (8193, 1, 8192, 1),
1008 (16385, 0, 8192, 3),
1009 (32768, 0, 32768, 1),
1010 (32769, 0, 32768, 2),
1011 ] {
1012 assert_eq!(
1013 group_count(blocks, first, bpg),
1014 groups,
1015 "blocks={blocks} first_data_block={first} blocks_per_group={bpg}"
1016 );
1017 }
1018 }
1019}
1020
1021#[cfg(test)]
1022mod desc_size_tests {
1023 use super::*;
1024
1025 fn raw(desc_size: u16, sixty_four_bit: bool) -> Vec<u8> {
1026 let mut raw = vec![0u8; SUPERBLOCK_SIZE];
1027 raw[0x38..0x3A].copy_from_slice(&EXT4_MAGIC.to_le_bytes());
1028 raw[0x00..0x04].copy_from_slice(&8192u32.to_le_bytes()); raw[0x04..0x08].copy_from_slice(&65536u32.to_le_bytes()); raw[0x18..0x1C].copy_from_slice(&2u32.to_le_bytes()); raw[0x20..0x24].copy_from_slice(&32768u32.to_le_bytes()); raw[0x28..0x2C].copy_from_slice(&4096u32.to_le_bytes()); raw[0x4C..0x50].copy_from_slice(&1u32.to_le_bytes()); raw[0x58..0x5A].copy_from_slice(&256u16.to_le_bytes()); if sixty_four_bit {
1036 let incompat = crate::features::Incompat::BIT64.bits();
1037 raw[0x60..0x64].copy_from_slice(&incompat.to_le_bytes());
1038 }
1039 raw[0xFE..0x100].copy_from_slice(&desc_size.to_le_bytes());
1040 raw
1041 }
1042
1043 #[test]
1046 fn without_64bit_the_descriptor_is_32_bytes_whatever_the_field_says() {
1047 for field in [0u16, 32, 64, 128, 1024, 33] {
1048 let sb = Superblock::parse(raw(field, false))
1049 .unwrap_or_else(|e| panic!("field {field}: {e:?}"));
1050 assert_eq!(sb.desc_size, 32, "field {field}");
1051 }
1052 }
1053
1054 #[test]
1056 fn with_64bit_the_field_is_range_checked() {
1057 for field in [64u16, 128, 1024] {
1058 assert_eq!(
1059 Superblock::parse(raw(field, true)).unwrap().desc_size,
1060 field
1061 );
1062 }
1063 for field in [0u16, 32, 96, 2048] {
1064 assert!(
1065 matches!(Superblock::parse(raw(field, true)), Err(Error::Corrupt(_))),
1066 "field {field} must be refused"
1067 );
1068 }
1069 }
1070}