use super::{mkfs::read_superblock, *};
impl Ext4FileSystem {
fn create_root_dir<B: BlockDevice>(&mut self, block_dev: &mut Jbd2Dev<B>) -> Ext4Result<()> {
create_root_directory_entry(self, block_dev)
}
fn dirty_for_mount(superblock: &mut Ext4Superblock) {
superblock.s_state &= !Ext4Superblock::EXT4_VALID_FS;
superblock.s_mnt_count = superblock.s_mnt_count.saturating_add(1);
}
fn inode_cache_size(superblock: &Ext4Superblock) -> usize {
match superblock.s_inode_size {
0 => DEFAULT_INODE_SIZE as usize,
n => n as usize,
}
}
fn reset_runtime_from_superblock<B: BlockDevice>(
&mut self,
block_dev: &mut Jbd2Dev<B>,
) -> Ext4Result<()> {
self.group_count = self.superblock.block_groups_count();
self.group_descs =
Self::load_group_descriptors(block_dev, &self.superblock, self.group_count)?;
self.block_allocator = BlockAllocator::new(&self.superblock);
self.inode_allocator = InodeAllocator::new(&self.superblock);
self.bitmap_cache = BitmapCache::create_default();
self.inodetable_cahce =
InodeCache::new(INODE_CACHE_MAX, Self::inode_cache_size(&self.superblock));
self.datablock_cache = DataBlockCache::new(DATABLOCK_CACHE_MAX, BLOCK_SIZE);
Ok(())
}
fn reload_after_journal_replay<B: BlockDevice>(
&mut self,
block_dev: &mut Jbd2Dev<B>,
) -> Ext4Result<()> {
self.superblock = read_superblock(block_dev).map_err(|_| Ext4Error::io())?;
self.superblock.verify_superblock()?;
Self::dirty_for_mount(&mut self.superblock);
self.reset_runtime_from_superblock(block_dev)
}
fn clear_recovery_state(&mut self) {
self.superblock.s_feature_incompat &= !Ext4Superblock::EXT4_FEATURE_INCOMPAT_RECOVER;
}
fn set_recovery_state(&mut self) {
self.superblock.s_feature_incompat |= Ext4Superblock::EXT4_FEATURE_INCOMPAT_RECOVER;
}
fn valid_lost_found_hint<B: BlockDevice>(
&mut self,
block_dev: &mut Jbd2Dev<B>,
) -> Ext4Result<bool> {
let ino = self.superblock.s_lpf_ino;
if ino == 0 {
return Ok(false);
}
let inode = self.get_inode_by_num(block_dev, InodeNumber::new(ino)?)?;
Ok(inode.i_mode != 0 && inode.is_dir())
}
fn journal_blocks<B: BlockDevice>(
&mut self,
block_dev: &mut Jbd2Dev<B>,
journal_inode: &mut Ext4Inode,
) -> Ext4Result<Vec<AbsoluteBN>> {
let journal_block_count = journal_inode.size().div_ceil(BLOCK_SIZE as u64);
let journal_block_map = resolve_inode_block_allextend(self, block_dev, journal_inode)?;
let mut journal_blocks = Vec::new();
for logical in 0..journal_block_count {
let logical = u32::try_from(logical).map_err(|_| Ext4Error::corrupted())?;
let phys = journal_block_map
.get(&logical)
.copied()
.ok_or_else(Ext4Error::corrupted)?;
journal_blocks.push(phys);
}
Ok(journal_blocks)
}
pub fn mount<B: BlockDevice>(block_dev: &mut Jbd2Dev<B>) -> Result<Self, Ext4Error> {
debug!("Start mounting Ext4 filesystem...");
let mut superblock = read_superblock(block_dev).map_err(|_| Ext4Error::io())?;
if superblock.s_magic != EXT4_SUPER_MAGIC {
error!(
"Invalid magic: {:#x}, expected: {:#x}",
superblock.s_magic, EXT4_SUPER_MAGIC
);
return Err(Ext4Error::invalid_magic());
}
debug!("Superblock magic verified");
superblock.verify_superblock()?;
if superblock.s_state & Ext4Superblock::EXT4_ERROR_FS != 0 {
warn!("Filesystem is in error state");
}
Self::dirty_for_mount(&mut superblock);
let group_count = superblock.block_groups_count();
debug!("Block group count: {group_count}");
let group_descs = Self::load_group_descriptors(block_dev, &superblock, group_count)?;
debug!("Loaded {} group descriptors", group_descs.len());
let block_allocator = BlockAllocator::new(&superblock);
let inode_allocator = InodeAllocator::new(&superblock);
debug!("Allocators initialized");
let bitmap_cache = BitmapCache::create_default();
debug!("Bitmap cache initialized (lazy loading)");
let inode_cache = InodeCache::new(INODE_CACHE_MAX, Self::inode_cache_size(&superblock));
debug!("Inode cache initialized");
let datablock_cache = DataBlockCache::new(DATABLOCK_CACHE_MAX, BLOCK_SIZE);
debug!("Data block cache initialized");
let mut fs = Self {
superblock,
group_descs,
block_allocator,
inode_allocator,
bitmap_cache,
root_inode: InodeNumber::new(2)?,
inodetable_cahce: inode_cache,
datablock_cache,
group_count,
mounted: true,
journal_sb_block_start: None,
};
debug_super_and_desc(&fs.superblock, &fs);
{
let needs_recovery = fs
.superblock
.has_feature_incompat(Ext4Superblock::EXT4_FEATURE_INCOMPAT_RECOVER);
if fs.superblock.has_journal() {
let journal_inode_num = InodeNumber::new(JOURNAL_FILE_INODE as u32)?;
let journal_inode = fs
.get_inode_by_num(block_dev, journal_inode_num)
.inspect_err(|e| {
error!("Failed to load journal inode {journal_inode_num}: {e}");
})?;
let journal_exists = journal_inode.i_mode != 0;
if fs
.superblock
.has_feature_compat(Ext4Superblock::EXT4_FEATURE_COMPAT_HAS_JOURNAL)
&& !journal_exists
{
if needs_recovery {
error!("Journal inode missing while filesystem needs recovery");
return Err(Ext4Error::corrupted());
}
create_journal_entry(&mut fs, block_dev).expect("create journal entry failed");
}
}
if needs_recovery && !fs.superblock.has_journal() {
error!("Filesystem needs journal recovery, but no journal is present");
return Err(Ext4Error::corrupted());
}
if (block_dev.is_use_journal() || needs_recovery) && fs.superblock.has_journal() {
let mut j_inode = fs
.get_inode_by_num(block_dev, InodeNumber::new(JOURNAL_FILE_INODE as u32)?)
.expect("load journal inode failed");
let journal_blocks =
fs.journal_blocks(block_dev, &mut j_inode)
.inspect_err(|e| {
error!("Failed to resolve journal blocks: {e}");
})?;
let journal_first_block = journal_blocks.first().copied().ok_or_else(|| {
error!("Journal has no mapped blocks");
Ext4Error::corrupted()
})?;
fs.journal_sb_block_start = Some(journal_first_block);
let journal_data = fs
.datablock_cache
.get_or_load(block_dev, journal_first_block)
.expect("load journal superblock block failed")
.data
.clone();
let j_sb = JournalSuperBllockS::from_disk_bytes(&journal_data);
block_dev.set_journal_superblock_with_mapping(j_sb, journal_blocks)?;
if needs_recovery {
let original_journal_use = block_dev.is_use_journal();
if !original_journal_use {
info!("Filesystem needs journal recovery; enabling replay for mount");
block_dev.set_journal_use(true);
}
let replay_status = block_dev.journal_replay_checked();
block_dev.set_journal_use(original_journal_use);
if replay_status != ReplayStatus::Complete {
error!("Journal replay did not complete: status={replay_status:?}");
return Err(Ext4Error::corrupted());
}
fs.reload_after_journal_replay(block_dev)?;
fs.clear_recovery_state();
} else if block_dev.is_use_journal() {
fs.set_recovery_state();
}
}
if !fs.superblock.has_journal() {
block_dev.set_journal_use(false);
}
}
{
let root_inode = fs.get_root(block_dev).map_err(|e| {
error!("Failed to load root inode: {e}");
Ext4Error::io()
})?;
if root_inode.i_mode == 0 || !root_inode.is_dir() {
warn!(
"Root inode is uninitialized or not a directory, creating root and \
lost+found... i_mode: {}, is_dir: {}",
root_inode.i_mode,
root_inode.is_dir()
);
fs.create_root_dir(block_dev).map_err(|_| Ext4Error::io())?;
}
}
{
if fs.valid_lost_found_hint(block_dev)? {
let ino = fs.superblock.s_lpf_ino;
info!("/lost+found exists (superblock hint inode={ino})");
} else {
if fs.superblock.s_lpf_ino != 0 {
let ino = fs.superblock.s_lpf_ino;
warn!("s_lpf_ino={ino} is not a valid directory, falling back to path scan");
}
match get_file_inode(&mut fs, block_dev, "/lost+found") {
Ok(Some((ino, inode))) if inode.is_dir() => {
fs.superblock.s_lpf_ino = ino.raw();
fs.sync_superblock(block_dev)?;
info!("/lost+found exists (path resolution, repaired hint inode={ino})");
}
Ok(Some((_ino, _inode))) => {
error!("/lost+found exists but is not a directory");
return Err(Ext4Error::corrupted());
}
Ok(None) => {
info!("/lost+found not found by path scan;will create!");
if create_lost_found_directory(&mut fs, block_dev).is_err() {
warn!("/lost+found missing and create failed");
}
}
Err(err) => {
error!("Failed to resolve /lost+found: {err}");
return Err(err);
}
}
}
}
{
let g0 = match fs.group_descs.first() {
Some(desc) => desc,
None => return Err(Ext4Error::bad_superblock()),
};
let inode_bitmap_blk = g0.inode_bitmap();
let data_bitmap_blk = g0.block_bitmap();
let inode_cache_key = CacheKey::new_inode(BGIndex::new(0));
let data_cache_key = CacheKey::new_block(BGIndex::new(0));
let inode_bitmap_data = fs
.bitmap_cache
.get_or_load(
block_dev,
inode_cache_key,
AbsoluteBN::new(inode_bitmap_blk),
)
.expect("block read failed")
.clone();
let blockbitmap_data = fs
.bitmap_cache
.get_or_load(block_dev, data_cache_key, AbsoluteBN::new(data_bitmap_blk))
.expect("block read failed");
if ext4_superblock_has_metadata_csum(&fs.superblock) {
if !g0.is_inode_bitmap_uninit() {
let stored_inode = g0.inode_bitmap_csum(&fs.superblock);
let computed_inode =
ext4_inode_bitmap_csum32(&fs.superblock, &inode_bitmap_data.data);
let expected_inode = computed_inode;
if !g0.inode_bitmap_csum_matches(&fs.superblock, expected_inode) {
error!(
"Inode bitmap checksum mismatch group=0 expected={expected_inode:#x} \
stored={stored_inode:#x} inode_bitmap_block={inode_bitmap_blk} \
inode_table_block={} flags={:#x}",
g0.inode_table(),
g0.bg_flags
);
return Err(Ext4Error::checksum());
}
}
if !g0.is_block_bitmap_uninit() {
let stored_block = g0.block_bitmap_csum(&fs.superblock);
let computed_block =
ext4_block_bitmap_csum32(&fs.superblock, &blockbitmap_data.data);
let expected_block = computed_block;
if !g0.block_bitmap_csum_matches(&fs.superblock, expected_block) {
error!(
"Block bitmap checksum mismatch group=0 expected={expected_block:#x} \
stored={stored_block:#x} block_bitmap_block={data_bitmap_blk} \
inode_table_block={} flags={:#x}",
g0.inode_table(),
g0.bg_flags
);
return Err(Ext4Error::checksum());
}
}
}
let mut inode_count: u64 = 0;
let mut datablock_count: u64 = 0;
let inode_data_array = &inode_bitmap_data.data;
let datablock_array = &blockbitmap_data.data;
inode_data_array.iter().for_each(|&bit| {
let mut tmp = bit;
loop {
if tmp == 0 {
break;
}
if tmp & 0x1 == 0x1 {
inode_count += 1;
}
tmp >>= 1;
}
});
datablock_array.iter().for_each(|&bit| {
let mut tmp = bit;
loop {
if tmp == 0 {
break;
}
if tmp & 0x1 == 0x1 {
datablock_count += 1;
}
tmp >>= 1;
}
});
debug!(
"Bitmap usage: inodes used = {inode_count}, data blocks used = {datablock_count}"
);
}
info!("Ext4 filesystem mounted");
info!(" - block size: {} bytes", fs.superblock.block_size());
info!(" - total blocks: {}", fs.superblock.blocks_count());
info!(" - free blocks: {}", fs.superblock.free_blocks_count());
info!(" - total inodes: {}", fs.superblock.s_inodes_count);
info!(" - free inodes: {}", fs.superblock.s_free_inodes_count);
fs.sync_filesystem(block_dev)?;
block_dev.umount_commit();
Ok(fs)
}
fn load_group_descriptors<B: BlockDevice>(
block_dev: &mut Jbd2Dev<B>,
superblock: &Ext4Superblock,
group_count: u32,
) -> Result<Vec<Ext4GroupDesc>, Ext4Error> {
let mut group_descs = Vec::new();
let gdt_base: u64 = BLOCK_SIZE as u64;
let mut current_block: Option<AbsoluteBN> = None;
let desc_size = superblock.get_desc_size() as usize;
debug!("Loading group descriptors: {group_count} groups, desc_size = {desc_size} bytes");
for group_id in 0..group_count {
let byte_offset = gdt_base + group_id as u64 * desc_size as u64;
let block_size_u64 = BLOCK_SIZE as u64;
let block_num = AbsoluteBN::new(byte_offset / block_size_u64);
let in_block = (byte_offset % block_size_u64) as usize;
if current_block != Some(block_num) {
block_dev
.read_block(block_num)
.map_err(|_| Ext4Error::io())?;
current_block = Some(block_num);
}
let buffer = block_dev.buffer();
let end = in_block + desc_size;
if end > buffer.len() {
error!(
"GDT out of range: group_id={}, in_block={}, desc_size={}, buffer_len={}",
group_id,
in_block,
desc_size,
buffer.len()
);
return Err(Ext4Error::bad_superblock());
}
let desc = Ext4GroupDesc::from_disk_bytes(&buffer[in_block..end]);
desc.verify_checksum(superblock, group_id)?;
group_descs.push(desc);
}
debug!(
"Successfully loaded {} group descriptors",
group_descs.len()
);
Ok(group_descs)
}
}
pub fn mount<B: BlockDevice>(block_dev: &mut Jbd2Dev<B>) -> Ext4Result<Ext4FileSystem> {
match Ext4FileSystem::mount(block_dev) {
Ok(_fs) => {
info!("Ext4 filesystem mounted");
Ok(_fs)
}
Err(e) => {
error!("Mount failed: {e}");
Err(e)
}
}
}