#![forbid(unsafe_code)]
use tempfile::TempDir;
use crate::store::transaction::CrashHooks;
use crate::store::{NewEntry, Store, StoreConfig};
fn create_store(dir: &TempDir) -> Store {
let cfg = StoreConfig {
segment_size: 1024 * 1024,
..Default::default()
};
Store::create(dir.path(), &cfg, [0xBB; 16]).unwrap()
}
fn ino(store: &mut Store) -> u64 {
store
.create_entry(
1,
b"f",
NewEntry::file(0o644, 1000, 1000),
&CrashHooks::none(),
)
.unwrap()
}
fn power_loss_truncate(dir: &TempDir, cutoff: u64) {
let segs: Vec<_> = std::fs::read_dir(dir.path().join("segments"))
.unwrap()
.map(|e| e.unwrap().path())
.filter(|p| p.extension().map(|e| e == "seg").unwrap_or(false))
.collect();
for p in &segs {
if let Ok(md) = std::fs::metadata(p) {
if md.len() > cutoff {
let f = std::fs::OpenOptions::new().write(true).open(p).unwrap();
f.set_len(cutoff).unwrap();
}
}
}
}
#[test]
fn deferred_writes_survive_process_crash() {
let dir = TempDir::new().unwrap();
let mut store = create_store(&dir);
let f = ino(&mut store);
store.write_region(f, 0, b"process-crash-safe").unwrap();
drop(store);
let store = Store::open(dir.path(), &StoreConfig::default()).unwrap();
let read = store.read_file(f, 0, 64).unwrap();
assert_eq!(read, b"process-crash-safe");
}
#[test]
fn power_loss_keeps_only_barrier_d_data_and_never_wedges() {
let dir = TempDir::new().unwrap();
let mut store = create_store(&dir);
let f = ino(&mut store);
store.write_region(f, 0, b"fsynced-data").unwrap();
store.durability_barrier(&CrashHooks::none()).unwrap();
let seg0 = dir.path().join("segments/0000000000000000.seg");
let durable_size = std::fs::metadata(&seg0).unwrap().len();
store.write_region(f, 0, b"LOST-ON-POWER-FAILURE").unwrap();
store.write_region(f, 0, b"ALSO-LOST").unwrap();
power_loss_truncate(&dir, durable_size);
drop(store);
let store = Store::open(dir.path(), &StoreConfig::default()).unwrap();
let read = store.read_file(f, 0, 64).unwrap();
assert_eq!(
read, b"fsynced-data",
"power loss must revert to the barrier'd state"
);
let mut store = store;
store.write_region(f, 0, b"post-power").unwrap();
store.durability_barrier(&CrashHooks::none()).unwrap();
let read = store.read_file(f, 0, 64).unwrap();
assert!(read.starts_with(b"post-power"), "got {read:?}");
let report = crate::fsck::fsck(dir.path(), &crate::fsck::FsckOptions::default()).unwrap();
assert!(
report.is_clean(),
"fsck after power-loss recovery: {}",
report.render()
);
}
#[test]
fn recovery_falls_back_to_newest_valid_root_record() {
let dir = TempDir::new().unwrap();
let mut store = create_store(&dir);
let f = ino(&mut store);
store.write_region(f, 0, b"root-a").unwrap();
store.durability_barrier(&CrashHooks::none()).unwrap();
let seg0 = dir.path().join("segments/0000000000000000.seg");
let barrier_size = std::fs::metadata(&seg0).unwrap().len();
store.write_region(f, 0, b"root-b").unwrap();
store.write_region(f, 0, b"root-c").unwrap();
power_loss_truncate(&dir, barrier_size);
let sb_path = dir.path().join("superblock");
for off in [0u64, 4096] {
use std::io::{Seek, SeekFrom, Write};
let mut fh = std::fs::OpenOptions::new()
.write(true)
.open(&sb_path)
.unwrap();
fh.seek(SeekFrom::Start(off)).unwrap();
fh.write_all(&[0xFF; 512]).unwrap();
}
drop(store);
let store = Store::open(dir.path(), &StoreConfig::default()).unwrap();
let read = store.read_file(f, 0, 64).unwrap();
assert_eq!(
read, b"root-a",
"fallback must recover the last barrier'd root"
);
let mut store = store;
store.write_region(f, 0, b"root-d").unwrap();
store.durability_barrier(&CrashHooks::none()).unwrap();
let read = store.read_file(f, 0, 64).unwrap();
assert_eq!(read, b"root-d");
let report = crate::fsck::fsck(dir.path(), &crate::fsck::FsckOptions::default()).unwrap();
assert!(
report.is_clean(),
"fsck after fallback recovery: {}",
report.render()
);
}