use crate::{
buffer::ExtentBuffer, filesystem::Filesystem, transaction::Transaction,
};
use std::io::{self, Read, Seek, Write};
const HEADER_FLAG_WRITTEN: u64 = 1 << 0;
const HEADER_FLAG_RELOC: u64 = 1 << 1;
pub fn cow_block<R: Read + Write + Seek>(
trans: &mut Transaction<R>,
fs_info: &mut Filesystem<R>,
eb: &ExtentBuffer,
tree_id: u64,
_parent_info: Option<(u64, usize)>,
) -> io::Result<ExtentBuffer> {
if eb.generation() == fs_info.generation
&& !fs_info.is_written(eb.logical())
{
return Ok(eb.clone());
}
let level = eb.level();
let new_logical = trans.alloc_tree_block(fs_info, tree_id, level)?;
let mut new_eb = eb.clone();
new_eb.set_logical(new_logical);
new_eb.set_bytenr(new_logical);
new_eb.set_generation(fs_info.generation);
let flags = new_eb.flags() & !(HEADER_FLAG_WRITTEN | HEADER_FLAG_RELOC);
new_eb.set_flags(flags);
debug_assert_eq!(
new_eb.generation(),
fs_info.generation,
"cow_block: new block at {new_logical} has wrong generation",
);
debug_assert_eq!(
new_eb.bytenr(),
new_logical,
"cow_block: bytenr/logical mismatch after COW",
);
debug_assert_eq!(
new_eb.flags() & HEADER_FLAG_WRITTEN,
0,
"cow_block: WRITTEN flag not cleared on new block",
);
debug_assert_eq!(
new_eb.level(),
eb.level(),
"cow_block: level changed during COW",
);
debug_assert_eq!(
new_eb.nritems(),
eb.nritems(),
"cow_block: nritems changed during COW",
);
trans
.delayed_refs
.drop_ref(eb.logical(), true, tree_id, level);
trans.pin_block(eb.logical());
debug_assert!(
trans.is_pinned(eb.logical()),
"cow_block: old block at {} not pinned after COW",
eb.logical(),
);
fs_info.mark_dirty(&new_eb);
Ok(new_eb)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn header_flag_values() {
assert_eq!(HEADER_FLAG_WRITTEN, 1);
assert_eq!(HEADER_FLAG_RELOC, 2);
}
#[test]
fn cow_skip_condition_generation_match_not_written() {
let mut eb = ExtentBuffer::new_zeroed(4096, 65536);
eb.set_generation(42);
let current_gen = 42u64;
let is_written = false;
assert!(
eb.generation() == current_gen && !is_written,
"should skip COW"
);
}
#[test]
fn cow_required_generation_mismatch() {
let mut eb = ExtentBuffer::new_zeroed(4096, 65536);
eb.set_generation(41);
let current_gen = 42u64;
let is_written = false;
let skip_cow = eb.generation() == current_gen && !is_written;
assert!(!skip_cow, "should require COW");
}
#[test]
fn cow_required_when_written() {
let mut eb = ExtentBuffer::new_zeroed(4096, 65536);
eb.set_generation(42);
let current_gen = 42u64;
let is_written = true;
let skip_cow = eb.generation() == current_gen && !is_written;
assert!(
!skip_cow,
"should require COW even with matching generation"
);
}
#[test]
fn clear_written_reloc_flags() {
let mut eb = ExtentBuffer::new_zeroed(4096, 65536);
eb.set_flags(HEADER_FLAG_WRITTEN | HEADER_FLAG_RELOC | 0x100);
let cleared = eb.flags() & !(HEADER_FLAG_WRITTEN | HEADER_FLAG_RELOC);
assert_eq!(cleared, 0x100);
assert_eq!(cleared & HEADER_FLAG_WRITTEN, 0);
assert_eq!(cleared & HEADER_FLAG_RELOC, 0);
}
}