#![forbid(unsafe_code)]
use crate::optimizer::foreground::ForegroundPolicy;
use crate::optimizer::policy::OptimizeOptions;
use crate::store::transaction::CrashHooks;
use crate::store::{NewEntry, Store, StoreConfig};
use tempfile::TempDir;
fn create_store(dir: &TempDir) -> Store {
let cfg = StoreConfig {
segment_size: 128 * 1024 * 1024,
..Default::default()
};
Store::create(dir.path(), &cfg, [0x5d; 16]).unwrap()
}
fn root_ino(store: &Store) -> u64 {
store.current_root().root_dir_ino
}
fn noise(n: usize, seed: u64) -> Vec<u8> {
let mut state = seed;
let mut out = Vec::with_capacity(n);
while out.len() < n {
state = state.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = state;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^= z >> 31;
let b = z.to_le_bytes();
let take = (n - out.len()).min(8);
out.extend_from_slice(&b[..take]);
}
out
}
fn drift_text(n_chunks: usize) -> Vec<u8> {
let chunk = 65536usize;
let mut out = Vec::with_capacity(n_chunks * chunk);
for c in 0..n_chunks {
for i in 0..chunk {
let mut b = b'a' + ((i / 7) % 23) as u8;
if i % 97 == 0 {
b = b"fn main() { return 0; }"[i % 23];
}
if i == c * 1009 % chunk {
b = b'X';
}
out.push(b);
}
}
out
}
#[test]
fn epoch_create_write_setattr_roundtrip_and_checkpoint() {
let dir = TempDir::new().unwrap();
let store = create_store(&dir);
let hooks = &CrashHooks::none();
let root = root_ino(&store);
let a = store
.epoch_create(root, b"a", NewEntry::file(0o644, 1000, 1000), hooks)
.unwrap();
let b = store
.epoch_create(root, b"b", NewEntry::file(0o644, 1000, 1000), hooks)
.unwrap();
let d = store
.epoch_create(root, b"d", NewEntry::dir(0o755, 1000, 1000), hooks)
.unwrap();
let ep = store.epoch();
assert!(store.dir_lookup_epoch(&ep, root, b"a").unwrap().is_some());
assert!(store.dir_lookup_epoch(&ep, root, b"b").unwrap().is_some());
assert!(store.dir_lookup_epoch(&ep, root, b"d").unwrap().is_some());
assert!(store.get_inode_epoch(&ep, a).unwrap().is_some());
assert!(store.get_inode_epoch(&ep, d).unwrap().unwrap().is_dir());
drop(ep);
let small = b"hello epoch world".to_vec();
store
.epoch_write(
a,
0,
&small,
OptimizeOptions::default(),
ForegroundPolicy::full(),
hooks,
)
.unwrap();
let big = drift_text(6);
store
.epoch_write(
b,
0,
&big,
OptimizeOptions::default(),
ForegroundPolicy::full(),
hooks,
)
.unwrap();
let ep = store.epoch();
assert_eq!(
store.read_file_epoch(&ep, a, 0, 1024).unwrap(),
small,
"overlay read of a small file"
);
assert_eq!(
store.read_file_epoch(&ep, b, 0, big.len() as u64).unwrap(),
big,
"overlay read of a multi-chunk file"
);
drop(ep);
let updated = store
.epoch_setattr(
a,
&crate::store::AttrUpdate {
mode: Some(0o600),
..Default::default()
},
hooks,
)
.unwrap();
assert_eq!(updated.mode & 0o777, 0o600);
store.epoch_checkpoint(hooks).unwrap();
assert!(store.epoch().is_empty(), "checkpoint clears the epoch");
let back_a = store.read_file(a, 0, small.len() as u64).unwrap();
assert_eq!(back_a, small);
let back_b = store.read_file(b, 0, big.len() as u64).unwrap();
assert_eq!(back_b, big);
let inode_a = store.get_inode(a).unwrap().unwrap();
assert_eq!(inode_a.mode & 0o777, 0o600);
assert_eq!(inode_a.size, small.len() as u64);
assert_eq!(store.dir_lookup(root, b"d").unwrap().unwrap().ino, d);
let report = crate::fsck::fsck(dir.path(), &crate::fsck::FsckOptions::default()).unwrap();
assert!(report.is_clean(), "fsck: {}", report.render());
}
#[test]
fn epoch_unlink_rename_semantics() {
let dir = TempDir::new().unwrap();
let store = create_store(&dir);
let hooks = &CrashHooks::none();
let root = root_ino(&store);
let a = store
.epoch_create(root, b"a", NewEntry::file(0o644, 1000, 1000), hooks)
.unwrap();
let b = store
.epoch_create(root, b"b", NewEntry::file(0o644, 1000, 1000), hooks)
.unwrap();
let out = store.epoch_rename(root, b"a", root, b"c", hooks).unwrap();
assert_eq!(out.src_ino, a);
assert!(out.replaced_dst_ino.is_none());
let ep = store.epoch();
assert!(store.dir_lookup_epoch(&ep, root, b"a").unwrap().is_none());
assert!(store.dir_lookup_epoch(&ep, root, b"c").unwrap().is_some());
drop(ep);
let out = store.epoch_rename(root, b"c", root, b"b", hooks).unwrap();
assert_eq!(out.replaced_dst_ino, Some(b));
let ep = store.epoch();
assert!(store.dir_lookup_epoch(&ep, root, b"c").unwrap().is_none());
assert_eq!(
store
.dir_lookup_epoch(&ep, root, b"b")
.unwrap()
.unwrap()
.ino,
a
);
drop(ep);
let removed = store.epoch_unlink(root, b"b", false, hooks).unwrap();
assert_eq!(removed, a);
let ep = store.epoch();
assert!(store.dir_lookup_epoch(&ep, root, b"b").unwrap().is_none());
drop(ep);
assert!(store.epoch_unlink(root, b"missing", true, hooks).is_err());
let d = store
.epoch_create(root, b"d", NewEntry::dir(0o755, 1000, 1000), hooks)
.unwrap();
let e = store
.epoch_create(root, b"e", NewEntry::file(0o644, 1000, 1000), hooks)
.unwrap();
assert!(store.epoch_rename(root, b"e", root, b"d", hooks).is_err());
let inner = store
.epoch_create(d, b"f", NewEntry::file(0o644, 1000, 1000), hooks)
.unwrap();
assert!(store.epoch_unlink(d, b"f", true, hooks).is_err());
store.epoch_unlink(d, b"f", false, hooks).unwrap();
assert!(store.epoch_unlink(d, b"f", true, hooks).is_err()); store.epoch_unlink(root, b"d", true, hooks).unwrap();
let _ = (e, inner);
store.epoch_checkpoint(hooks).unwrap();
assert!(store.dir_lookup(root, b"a").unwrap().is_none());
assert!(store.dir_lookup(root, b"c").unwrap().is_none());
assert!(store.dir_lookup(root, b"b").unwrap().is_none());
assert!(store.dir_lookup(root, b"d").unwrap().is_none());
assert!(store.get_inode(a).unwrap().is_none(), "removed inode");
let report = crate::fsck::fsck(dir.path(), &crate::fsck::FsckOptions::default()).unwrap();
assert!(report.is_clean(), "fsck: {}", report.render());
}
#[test]
fn epoch_crash_recovery_replays_uncheckpointed_log() {
let dir = TempDir::new().unwrap();
let hooks = &CrashHooks::none();
let a;
let data = drift_text(3);
{
let store = create_store(&dir);
let root = store.current_root().root_dir_ino;
a = store
.epoch_create(root, b"a", NewEntry::file(0o644, 1000, 1000), hooks)
.unwrap();
store
.epoch_write(
a,
0,
&data,
OptimizeOptions::default(),
ForegroundPolicy::full(),
hooks,
)
.unwrap();
store
.epoch_create(root, b"b", NewEntry::dir(0o755, 1000, 1000), hooks)
.unwrap();
}
let store = Store::open(dir.path(), &StoreConfig::default()).unwrap();
let root = store.current_root().root_dir_ino;
assert_eq!(store.read_file(a, 0, data.len() as u64).unwrap(), data);
assert!(store.dir_lookup(root, b"b").unwrap().is_some());
assert!(store.current_root().log_seq > 0, "replay consumed the log");
let report = crate::fsck::fsck(dir.path(), &crate::fsck::FsckOptions::default()).unwrap();
assert!(report.is_clean(), "fsck: {}", report.render());
drop(store);
let store = Store::open(dir.path(), &StoreConfig::default()).unwrap();
assert_eq!(store.read_file(a, 0, data.len() as u64).unwrap(), data);
assert!(store.dir_lookup(root, b"b").unwrap().is_some());
}
#[test]
fn epoch_checkpoint_after_replay_is_idempotent() {
let dir = TempDir::new().unwrap();
let hooks = &CrashHooks::none();
let a;
let data = noise(2 * 65536, 0xabcd);
{
let store = create_store(&dir);
let root = store.current_root().root_dir_ino;
a = store
.epoch_create(root, b"a", NewEntry::file(0o644, 1000, 1000), hooks)
.unwrap();
store
.epoch_write(
a,
0,
&data,
OptimizeOptions::default(),
ForegroundPolicy::full(),
hooks,
)
.unwrap();
}
let store = Store::open(dir.path(), &StoreConfig::default()).unwrap();
assert_eq!(store.read_file(a, 0, data.len() as u64).unwrap(), data);
store.epoch_checkpoint(hooks).unwrap();
assert_eq!(store.read_file(a, 0, data.len() as u64).unwrap(), data);
let report = crate::fsck::fsck(dir.path(), &crate::fsck::FsckOptions::default()).unwrap();
assert!(report.is_clean(), "fsck: {}", report.render());
}
#[test]
fn epoch_flushes_before_gc() {
let dir = TempDir::new().unwrap();
let store = create_store(&dir);
let hooks = &CrashHooks::none();
let root = root_ino(&store);
let a = store
.epoch_create(root, b"a", NewEntry::file(0o644, 1000, 1000), hooks)
.unwrap();
let data = noise(65536, 0x1234);
store
.epoch_write(
a,
0,
&data,
OptimizeOptions::default(),
ForegroundPolicy::full(),
hooks,
)
.unwrap();
crate::store::gc::collect(&store, hooks).unwrap();
assert!(store.epoch().is_empty(), "GC flushed the epoch");
assert_eq!(store.read_file(a, 0, data.len() as u64).unwrap(), data);
let report = crate::fsck::fsck(dir.path(), &crate::fsck::FsckOptions::default()).unwrap();
assert!(report.is_clean(), "fsck: {}", report.render());
}
#[test]
fn epoch_sequential_writes_form_chains_and_stay_exact() {
let dir = TempDir::new().unwrap();
let store = create_store(&dir);
let hooks = &CrashHooks::none();
let root = root_ino(&store);
let a = store
.epoch_create(root, b"a", NewEntry::file(0o644, 1000, 1000), hooks)
.unwrap();
let mut data = Vec::new();
for i in 0..6 {
let chunk = drift_text(1);
let off = (i * 65536) as u64;
store
.epoch_write(
a,
off,
&chunk,
OptimizeOptions::default(),
ForegroundPolicy::full(),
hooks,
)
.unwrap();
data.extend_from_slice(&chunk);
}
let patch = b"PATCHED-CONTENT".to_vec();
store
.epoch_write(
a,
100,
&patch,
OptimizeOptions::default(),
ForegroundPolicy::full(),
hooks,
)
.unwrap();
data[100..100 + patch.len()].copy_from_slice(&patch);
let ep = store.epoch();
let before = store.read_file_epoch(&ep, a, 0, data.len() as u64).unwrap();
assert_eq!(before, data, "overlay read before the checkpoint");
drop(ep);
store.epoch_checkpoint(hooks).unwrap();
assert_eq!(
store.read_file(a, 0, data.len() as u64).unwrap(),
data,
"committed read after the checkpoint"
);
let report = crate::fsck::fsck(dir.path(), &crate::fsck::FsckOptions::default()).unwrap();
assert!(report.is_clean(), "fsck: {}", report.render());
}
#[test]
fn epoch_duplicate_content_dedups_at_checkpoint() {
let dir = TempDir::new().unwrap();
let store = create_store(&dir);
let hooks = &CrashHooks::none();
let root = root_ino(&store);
let chunk = noise(65536, 0xfeed);
let a = store
.epoch_create(root, b"a", NewEntry::file(0o644, 1000, 1000), hooks)
.unwrap();
let b = store
.epoch_create(root, b"b", NewEntry::file(0o644, 1000, 1000), hooks)
.unwrap();
for (ino, _) in [(a, 0u64), (b, 0u64)] {
store
.epoch_write(
ino,
0,
&chunk,
OptimizeOptions::default(),
ForegroundPolicy::full(),
hooks,
)
.unwrap();
}
store.epoch_checkpoint(hooks).unwrap();
assert_eq!(store.read_file(a, 0, chunk.len() as u64).unwrap(), chunk);
assert_eq!(store.read_file(b, 0, chunk.len() as u64).unwrap(), chunk);
let cid = crate::core::extent::ChunkId::of(&chunk);
let descs = crate::store::index::scan_all(
store.current_root().chunk_index_root,
crate::store::BTREE_ORDER,
store.limits().max_fanout,
&store,
)
.unwrap();
assert_eq!(
descs
.iter()
.filter(|(k, _)| k.as_slice() == cid.as_bytes())
.count(),
1,
"the checkpoint must merge duplicate chunk-index entries"
);
let report = crate::fsck::fsck(dir.path(), &crate::fsck::FsckOptions::default()).unwrap();
assert!(report.is_clean(), "fsck: {}", report.render());
}