use fs_btrfs::btree::{header_offsets as o, HEADER_SIZE};
use fs_btrfs::chunk::DiskKey;
use fs_btrfs::fs::Filesystem;
use fs_btrfs::leaf_edit::{split, OwnedItem};
use fs_btrfs_test_support::{fixture, le32, le64};
use fs_core::FileDevice;
use std::path::Path;
use std::sync::Arc;
fn mount(path: &Path) -> Filesystem {
let dev = Arc::new(
FileDevice::open(path)
.unwrap_or_else(|error| panic!("opening {}: {error}", path.display())),
);
Filesystem::mount(dev).unwrap_or_else(|error| panic!("mounting {}: {error}", path.display()))
}
fn items_of(block: &[u8]) -> Vec<OwnedItem> {
let n = le32(block, o::NRITEMS) as usize;
(0..n)
.filter_map(|i| {
let at = HEADER_SIZE + i * 25;
let start = HEADER_SIZE + le32(block, at + 17) as usize;
let end = start + le32(block, at + 21) as usize;
(end <= block.len()).then(|| OwnedItem {
key: DiskKey {
objectid: le64(block, at),
key_type: block[at + 8],
offset: le64(block, at + 9),
},
data: block[start..end].to_vec(),
})
})
.collect()
}
fn live_fs_leaves(path: &Path) -> Vec<Vec<OwnedItem>> {
const ROOT_ITEM_KEY: u8 = 132;
const ROOT_ITEM_BYTENR: usize = 176;
let fs = mount(path);
let root = fs
.root_tree_items()
.unwrap_or_else(|error| panic!("reading the root tree of {}: {error}", path.display()))
.into_iter()
.find_map(|(objectid, key_type, _, data)| {
(objectid == 5 && key_type == ROOT_ITEM_KEY && data.len() >= ROOT_ITEM_BYTENR + 8)
.then(|| le64(&data, ROOT_ITEM_BYTENR))
})
.unwrap_or_else(|| panic!("{} holds no ROOT_ITEM for the fs tree", path.display()));
let mut out = Vec::new();
let mut stack = vec![root];
while let Some(at) = stack.pop() {
let block = fs.read_tree_block(at).unwrap_or_else(|error| {
panic!("reading the block at {at} of {}: {error}", path.display())
});
match block.body.key_ptrs() {
Some(ptrs) => {
for p in ptrs.iter().rev() {
stack.push(p.blockptr);
}
}
None => out.push(items_of(block.bytes())),
}
}
out
}
struct RealSplit {
all: Vec<OwnedItem>,
kernel_left: usize,
nodesize: usize,
}
fn real_split(pair: &str) -> RealSplit {
let before = fixture(&format!("btrfs-split{pair}-before.img"));
let after = fixture(&format!("btrfs-split{pair}-after.img"));
let nodesize = mount(&after).superblock().nodesize as usize;
let source = live_fs_leaves(&before);
let result = live_fs_leaves(&after);
assert_eq!(
result.len(),
source.len() + 1,
"btrfs-split{pair}: the fs tree went from {} live leaves to {}. A single split adds exactly \
one, so this pair does not hold the event it is supposed to.",
source.len(),
result.len()
);
let at = (0..source.len())
.find(|&i| source[i] != result[i])
.unwrap_or(source.len() - 1);
let mut all = result[at].clone();
all.extend_from_slice(&result[at + 1]);
assert!(
all.len() > source[at].len(),
"btrfs-split{pair}: a split adds the item that caused it"
);
RealSplit {
all,
kernel_left: result[at].len(),
nodesize,
}
}
fn check(pair: &str) -> (usize, usize, usize) {
let real = real_split(pair);
let (left, right) = split(&real.all).expect("splitting");
assert!(
!left.is_empty() && !right.is_empty(),
"btrfs-split{pair}: a half with nothing in it is not a leaf the tree has a use for"
);
let mut rejoined = left.clone();
rejoined.extend_from_slice(&right);
assert_eq!(
rejoined, real.all,
"btrfs-split{pair}: the two halves are not the items that went in"
);
let key = |i: &OwnedItem| (i.key.objectid, i.key.key_type, i.key.offset);
for half in [&left, &right] {
for w in half.windows(2) {
assert!(
key(&w[0]) < key(&w[1]),
"btrfs-split{pair}: a half is out of order at {:?}",
w[0].key
);
}
}
assert!(
key(left.last().unwrap()) < key(&right[0]),
"btrfs-split{pair}: the boundary is out of order — the left half ends at {:?} and the right \
begins at {:?}",
left.last().unwrap().key,
right[0].key
);
for (side, half) in [("left", &left), ("right", &right)] {
let need = HEADER_SIZE + half.len() * 25 + half.iter().map(|i| i.data.len()).sum::<usize>();
assert!(
need <= real.nodesize,
"btrfs-split{pair}: the {side} half needs {need} bytes and a leaf holds {}",
real.nodesize
);
}
(left.len(), right.len(), real.kernel_left)
}
#[test]
fn splitting_a_leaf_the_kernel_split_gives_two_valid_leaves() {
let (l, r, kernel) = check("");
eprintln!("even item sizes: {l} | {r} (the kernel put {kernel} on the left)");
}
#[test]
fn splitting_a_leaf_of_uneven_items_gives_two_valid_leaves() {
let (l, r, kernel) = check("-vary");
eprintln!("uneven item sizes: {l} | {r} (the kernel put {kernel} on the left)");
}
#[test]
fn the_kernels_own_boundary_is_recorded() {
for pair in ["", "-vary"] {
let real = real_split(pair);
let total = real.all.len();
eprintln!(
"kernel: {total} items -> {} | {} (half is {}, half+1 is {})",
real.kernel_left,
total - real.kernel_left,
total / 2,
total / 2 + 1
);
assert!(
real.kernel_left > total / 5 && real.kernel_left < total * 4 / 5,
"the kernel put {} of {total} items on the left, which is not a division \
near the middle at all",
real.kernel_left
);
}
}
#[test]
fn a_leaf_with_nothing_to_divide_is_refused() {
let one = vec![OwnedItem {
key: DiskKey {
objectid: 1,
key_type: 1,
offset: 0,
},
data: vec![0; 8],
}];
assert!(split(&one).is_err(), "one item cannot be split in two");
assert!(split(&[]).is_err(), "nor can none");
}