use deku::{DekuRead, DekuWrite};
use super::Ext2;
use super::error::Ext2Error;
use super::superblock::{SUPERBLOCK_SIZE, SUPERBLOCK_START_BYTE, Superblock};
use crate::dev::Device;
use crate::dev::address::Address;
use crate::error::Error;
use crate::fs::error::FsError;
pub const BLOCK_GROUP_DESCRIPTOR_SIZE: usize = 32;
#[derive(Debug, Clone, Copy, DekuRead, DekuWrite)]
#[deku(endian = "little")]
#[cfg_attr(test, derive(PartialEq, Eq))]
#[allow(clippy::module_name_repetitions)]
pub struct BlockGroupDescriptor {
pub block_bitmap: u32,
pub inode_bitmap: u32,
pub inode_table: u32,
pub free_blocks_count: u16,
pub free_inodes_count: u16,
pub used_dirs_count: u16,
pub pad: u16,
pub reserved: [u8; 12],
}
impl BlockGroupDescriptor {
pub const fn starting_addr(superblock: &Superblock, n: u32) -> Result<Address, Error<Ext2Error>> {
let block_group_count = superblock.block_group_count();
if block_group_count <= n {
return Err(Error::Fs(FsError::Implementation(Ext2Error::NonExistingBlockGroup(n))));
}
let superblock_end_address = SUPERBLOCK_START_BYTE + SUPERBLOCK_SIZE as u64;
Ok(Address::new(superblock_end_address + BLOCK_GROUP_DESCRIPTOR_SIZE as u64 * (n as u64)))
}
pub fn parse<Dev: Device>(fs: &Ext2<Dev>, n: u32) -> Result<Self, Error<Ext2Error>> {
let mut device = fs.device.lock();
let block_group_descriptor_address = Self::starting_addr(fs.superblock(), n)?;
let block_group_descriptor =
device.read_from_bytes::<Self>(block_group_descriptor_address, BLOCK_GROUP_DESCRIPTOR_SIZE)?;
Ok(block_group_descriptor)
}
pub(crate) unsafe fn write_on_device<Dev: Device>(
fs: &Ext2<Dev>,
n: u32,
block_group_descriptor: Self,
) -> Result<(), Error<Ext2Error>> {
let starting_addr = Self::starting_addr(fs.superblock(), n)?;
fs.device.lock().write_to_bytes(starting_addr, block_group_descriptor).map_err(Into::into)
}
}
#[cfg(test)]
mod test {
use core::mem::size_of;
use std::fs::File;
use super::{BLOCK_GROUP_DESCRIPTOR_SIZE, BlockGroupDescriptor};
use crate::fs::ext2::Ext2;
use crate::tests::new_device_id;
#[test]
fn struct_size() {
assert_eq!(size_of::<BlockGroupDescriptor>(), BLOCK_GROUP_DESCRIPTOR_SIZE);
}
fn parse_first_block_group_descriptor_base(file: File) {
let fs = Ext2::new(file, new_device_id()).unwrap();
assert!(BlockGroupDescriptor::parse(&fs, 0).is_ok());
}
fn parse_first_block_group_descriptor_extended(file: File) {
let fs = Ext2::new(file, new_device_id()).unwrap();
assert!(BlockGroupDescriptor::parse(&fs, 0).is_ok());
}
fn failed_parse_base(file: File) {
let fs = Ext2::new(file, new_device_id()).unwrap();
assert!(BlockGroupDescriptor::parse(&fs, fs.superblock().block_group_count()).is_err());
}
fn failed_parse_extended(file: File) {
let fs = Ext2::new(file, new_device_id()).unwrap();
assert!(BlockGroupDescriptor::parse(&fs, fs.superblock().block_group_count()).is_err());
}
fn write_back(file: File) {
let fs = Ext2::new(file, new_device_id()).unwrap();
let mut bgd = BlockGroupDescriptor::parse(&fs, 0).unwrap();
bgd.free_blocks_count = 0;
bgd.reserved = [0x9A; 12];
unsafe { BlockGroupDescriptor::write_on_device(&fs, 0, bgd).unwrap() };
let new_bgd = BlockGroupDescriptor::parse(&fs, 0).unwrap();
assert_eq!(bgd, new_bgd);
}
mod generated {
use crate::tests::{PostCheck, generate_fs_test};
generate_fs_test!(parse_first_block_group_descriptor_base, "./tests/fs/ext2/base.ext2", PostCheck::Ext);
generate_fs_test!(parse_first_block_group_descriptor_extended, "./tests/fs/ext2/extended.ext2", PostCheck::Ext);
generate_fs_test!(failed_parse_base, "./tests/fs/ext2/base.ext2", PostCheck::Ext);
generate_fs_test!(failed_parse_extended, "./tests/fs/ext2/extended.ext2", PostCheck::Ext);
generate_fs_test!(write_back, "./tests/fs/ext2/io_operations.ext2", PostCheck::None);
}
}