use fs_ext4::block_io::BlockDevice;
use fs_ext4::checksum::{group_desc_csum, Checksummer};
use fs_ext4::error::{Error, Result};
use fs_ext4::features::FsFlavor;
use fs_ext4::fs::Filesystem;
use fs_ext4::mkfs;
use std::sync::{Arc, Mutex};
struct MemDev {
bytes: Mutex<Vec<u8>>,
}
impl BlockDevice for MemDev {
fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<()> {
let b = self.bytes.lock().unwrap();
let start = offset as usize;
buf.copy_from_slice(&b[start..start + buf.len()]);
Ok(())
}
fn write_at(&self, offset: u64, buf: &[u8]) -> Result<()> {
let mut b = self.bytes.lock().unwrap();
let start = offset as usize;
b[start..start + buf.len()].copy_from_slice(buf);
Ok(())
}
fn size_bytes(&self) -> u64 {
self.bytes.lock().unwrap().len() as u64
}
fn is_writable(&self) -> bool {
true
}
fn flush(&self) -> Result<()> {
Ok(())
}
}
#[derive(Clone, Copy, Debug)]
enum Pointer {
BlockBitmap = 0x00,
InodeBitmap = 0x04,
InodeTable = 0x08,
}
const POINTERS: [Pointer; 3] = [
Pointer::BlockBitmap,
Pointer::InodeBitmap,
Pointer::InodeTable,
];
#[derive(Clone, Copy, Debug)]
enum Volume {
Ext4TwoKiBThreeGroups,
Ext2OneKiB,
}
fn format(volume: Volume) -> Arc<MemDev> {
let (bytes, block_size, flavor) = match volume {
Volume::Ext4TwoKiBThreeGroups => (80u64 << 20, 2048, FsFlavor::Ext4),
Volume::Ext2OneKiB => (8u64 << 20, 1024, FsFlavor::Ext2),
};
let dev = Arc::new(MemDev {
bytes: Mutex::new(vec![0u8; bytes as usize]),
});
mkfs::format_filesystem_with_flavor(dev.as_ref(), None, None, bytes, block_size, flavor)
.expect("format");
dev
}
fn set_pointer(dev: &Arc<MemDev>, group: u64, pointer: Pointer, value: u32) {
let fs = Filesystem::mount(dev.clone()).expect("the volume as formatted mounts");
let sb = fs.sb.clone();
drop(fs);
let bs = u64::from(sb.block_size());
let (block, off) = sb.descriptor_location(group);
let at = block * bs + off as u64;
let mut desc = vec![0u8; sb.desc_size as usize];
dev.read_at(at, &mut desc).unwrap();
let field = pointer as usize;
desc[field..field + 4].copy_from_slice(&value.to_le_bytes());
if desc.len() >= 64 {
desc[0x20 + field..0x24 + field].copy_from_slice(&0u32.to_le_bytes());
}
let csum = Checksummer::from_superblock(&sb);
if let Some(c) = group_desc_csum(&sb, &csum, group as u32, &desc) {
desc[0x1E..0x20].copy_from_slice(&c.to_le_bytes());
}
dev.write_at(at, &desc).unwrap();
}
fn assert_refused(dev: Arc<MemDev>, what: &str, because: &str) {
match Filesystem::mount(dev) {
Err(Error::Corrupt(why)) => assert!(
why.contains(because),
"{what}: refused, but not by the descriptor bound: {why}"
),
Err(other) => panic!("{what}: wrong error {other:?}"),
Ok(_) => panic!("{what}: mounted"),
}
}
const INTO_GROUP_ZERO: &str = "points into group 0's superblock or descriptor table";
const OUT_OF_ITS_GROUP: &str = "points outside its own group";
#[test]
fn the_volumes_as_formatted_mount() {
for volume in [Volume::Ext4TwoKiBThreeGroups, Volume::Ext2OneKiB] {
let fs = Filesystem::mount(format(volume))
.unwrap_or_else(|e| panic!("{volume:?} as formatted: {e:?}"));
if matches!(volume, Volume::Ext4TwoKiBThreeGroups) {
assert_eq!(fs.sb.block_group_count(), 3, "fixture: three groups");
}
}
}
#[test]
fn a_group_0_pointer_of_zero_is_refused() {
for volume in [Volume::Ext4TwoKiBThreeGroups, Volume::Ext2OneKiB] {
for pointer in POINTERS {
let dev = format(volume);
set_pointer(&dev, 0, pointer, 0);
assert_refused(dev, &format!("{volume:?} {pointer:?} = 0"), INTO_GROUP_ZERO);
}
}
}
#[test]
fn a_pointer_onto_the_superblock_or_descriptor_table_is_refused() {
for (volume, block) in [
(Volume::Ext4TwoKiBThreeGroups, 1),
(Volume::Ext2OneKiB, 1),
(Volume::Ext2OneKiB, 2),
] {
for pointer in POINTERS {
let dev = format(volume);
set_pointer(&dev, 0, pointer, block);
assert_refused(
dev,
&format!("{volume:?} {pointer:?} = {block}"),
INTO_GROUP_ZERO,
);
}
}
}
#[test]
fn without_flex_bg_a_pointer_outside_its_own_group_is_refused() {
let probe = Filesystem::mount(format(Volume::Ext4TwoKiBThreeGroups)).unwrap();
let bpg = probe.sb.blocks_per_group;
let first = probe.sb.first_data_block;
drop(probe);
let in_group_0 = first + bpg - 2048;
let in_group_1 = first + bpg + bpg - 2048;
for (group, target) in [(1u64, in_group_0), (0, in_group_1)] {
for pointer in POINTERS {
let dev = format(Volume::Ext4TwoKiBThreeGroups);
set_pointer(&dev, group, pointer, target);
assert_refused(
dev,
&format!("group {group} {pointer:?} = {target}"),
OUT_OF_ITS_GROUP,
);
}
}
}