#![allow(dead_code)]
use fs_ext4::bgd::BgdFlags;
use fs_ext4::block_io::BlockDevice;
use fs_ext4::error::{Error, Result};
use fs_ext4::fs::Filesystem;
use fs_ext4::mkfs;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
pub const BLOCK_SIZE: u32 = 2048;
pub const IMAGE_BYTES: u64 = 128 * 1024 * 1024;
pub const NEVER: usize = usize::MAX;
pub struct Faulty {
bytes: Mutex<Vec<u8>>,
writes: Mutex<Vec<u64>>,
seen: AtomicUsize,
fail_at: AtomicUsize,
}
impl Faulty {
pub fn new(size: u64) -> Arc<Self> {
Arc::new(Self {
bytes: Mutex::new(vec![0u8; size as usize]),
writes: Mutex::new(Vec::new()),
seen: AtomicUsize::new(0),
fail_at: AtomicUsize::new(NEVER),
})
}
pub fn arm(&self, fail_at: usize) {
self.writes.lock().unwrap().clear();
self.seen.store(0, Ordering::SeqCst);
self.fail_at.store(fail_at, Ordering::SeqCst);
}
pub fn writes(&self) -> Vec<u64> {
self.writes.lock().unwrap().clone()
}
pub fn bytes_snapshot(&self) -> Vec<u8> {
self.bytes.lock().unwrap().clone()
}
pub fn block(&self, block: u64) -> Vec<u8> {
let bs = BLOCK_SIZE as usize;
let at = block as usize * bs;
self.bytes.lock().unwrap()[at..at + bs].to_vec()
}
}
impl BlockDevice for Faulty {
fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<()> {
let b = self.bytes.lock().unwrap();
let start = offset as usize;
let end = start + buf.len();
assert!(end <= b.len(), "read past EOF");
buf.copy_from_slice(&b[start..end]);
Ok(())
}
fn write_at(&self, offset: u64, buf: &[u8]) -> Result<()> {
self.writes.lock().unwrap().push(offset);
if self.seen.fetch_add(1, Ordering::SeqCst) == self.fail_at.load(Ordering::SeqCst) {
return Err(Error::Corrupt("injected write failure"));
}
let mut b = self.bytes.lock().unwrap();
let start = offset as usize;
let end = start + buf.len();
assert!(end <= b.len(), "write past EOF");
b[start..end].copy_from_slice(buf);
Ok(())
}
fn size_bytes(&self) -> u64 {
IMAGE_BYTES
}
fn is_writable(&self) -> bool {
true
}
fn flush(&self) -> Result<()> {
Ok(())
}
}
pub const GARBAGE: u8 = 0xFF;
pub fn mark_group_uninit(dev: &Faulty, fs: &Filesystem, gi: usize) {
const INODE_UNINIT: u16 = 0x0001;
const BLOCK_UNINIT: u16 = 0x0002;
let bs = fs.sb.block_size() as u64;
let garbage = vec![GARBAGE; bs as usize];
for block in [fs.groups[gi].block_bitmap, fs.groups[gi].inode_bitmap] {
dev.write_at(block * bs, &garbage).expect("scribble bitmap");
}
let desc_size = fs.sb.desc_size as usize;
let desc_at = descriptor_offset(fs, gi);
let mut desc = vec![0u8; desc_size];
dev.read_at(desc_at, &mut desc).expect("read bgd");
let flags = u16::from_le_bytes(desc[0x12..0x14].try_into().unwrap());
desc[0x12..0x14].copy_from_slice(&(flags | INODE_UNINIT | BLOCK_UNINIT).to_le_bytes());
assert!(
fs.csum.enabled,
"the default format checksums its descriptors"
);
desc[0x1E..0x20].copy_from_slice(&[0, 0]);
let c = fs_ext4::checksum::linux_crc32c(fs.csum.seed, &(gi as u32).to_le_bytes());
let c = fs_ext4::checksum::linux_crc32c(c, &desc);
desc[0x1E..0x20].copy_from_slice(&(c as u16).to_le_bytes());
dev.write_at(desc_at, &desc).expect("write bgd");
}
pub fn descriptor_offset(fs: &Filesystem, gi: usize) -> u64 {
let bs = fs.sb.block_size() as u64;
let byte_in_bgt = gi as u64 * fs.sb.desc_size as u64;
let bgt_block = fs.sb.first_data_block as u64 + 1 + byte_in_bgt / bs;
bgt_block * bs + byte_in_bgt % bs
}
pub fn on_disk_flags(dev: &Faulty, fs: &Filesystem, gi: usize) -> BgdFlags {
let mut desc = [0u8; 2];
dev.read_at(descriptor_offset(fs, gi) + 0x12, &mut desc)
.expect("read flags");
BgdFlags::from_bits_truncate(u16::from_le_bytes(desc))
}
pub const TARGET_GROUP: usize = 2;
pub fn prepared() -> Arc<Faulty> {
let dev = Faulty::new(IMAGE_BYTES);
mkfs::format_filesystem(dev.as_ref(), Some("DIRECT"), None, IMAGE_BYTES, BLOCK_SIZE)
.expect("format");
{
let fs = Filesystem::mount(dev.clone()).expect("mount to locate groups");
assert!(fs.journal.is_none(), "the default format has no journal");
assert_eq!(fs.groups.len(), 4, "the layout this scenario is built on");
for gi in 1..=TARGET_GROUP {
mark_group_uninit(&dev, &fs, gi);
}
}
let fs = Filesystem::mount(dev.clone()).expect("mount");
fs.apply_mkdir("/spread", 0o755).expect("mkdir");
for name in ["/spread/fill", "/spread/f", "/spread/g"] {
fs.apply_create(name, 0o644).expect("create");
}
drop(fs);
let fs = Filesystem::mount(dev.clone()).expect("mount");
let free = fs.groups[1].free_blocks_count as usize;
fs.apply_pwrite("/spread/fill", 0, &vec![0x5a; free * BLOCK_SIZE as usize])
.expect("fill");
drop(fs);
dev
}
pub struct Outcome {
pub commit_writes: Vec<u64>,
pub first: Result<u64>,
pub second: Result<u64>,
pub bitmap_block: u64,
pub target_flags: BgdFlags,
pub target_bitmap: Vec<u8>,
pub state_after_drop: u16,
pub dev: Arc<Faulty>,
}
pub const FIRST: [u8; 4096] = [0xab; 4096];
pub const SECOND: [u8; 4096] = [0xcd; 4096];
pub fn run(fail_at: usize) -> Outcome {
let dev = prepared();
let fs = Filesystem::mount(dev.clone()).expect("mount");
assert!(
on_disk_flags(&dev, &fs, TARGET_GROUP).contains(BgdFlags::BLOCK_UNINIT),
"premise: the target group is still BLOCK_UNINIT"
);
fs.apply_utimens("/spread/g", 0, 0, 0, 0).expect("touch");
let bitmap_block = fs.groups[TARGET_GROUP].block_bitmap;
dev.arm(fail_at);
let first = fs.apply_pwrite("/spread/f", 0, &FIRST);
let commit_writes = dev.writes().iter().map(|o| o / BLOCK_SIZE as u64).collect();
dev.arm(NEVER);
let target_flags = on_disk_flags(&dev, &fs, TARGET_GROUP);
let target_bitmap = dev.block(bitmap_block);
let second = fs.apply_pwrite("/spread/g", 0, &SECOND);
drop(fs);
let mut state = [0u8; 2];
dev.read_at(1024 + 0x3A, &mut state).expect("read s_state");
Outcome {
commit_writes,
first,
second,
bitmap_block,
target_flags,
target_bitmap,
state_after_drop: u16::from_le_bytes(state),
dev,
}
}