use crate::raw::{
BTRFS_SAME_DATA_DIFFERS, btrfs_ioc_file_extent_same, btrfs_ioctl_same_args,
btrfs_ioctl_same_extent_info,
};
use std::{
mem,
os::{fd::AsRawFd, unix::io::BorrowedFd},
};
#[derive(Debug, Clone)]
pub struct DedupeTarget {
pub fd: BorrowedFd<'static>,
pub logical_offset: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DedupeResult {
Deduped(u64),
DataDiffers,
Error(i32),
}
#[allow(clippy::cast_possible_wrap)] pub fn file_extent_same(
src_fd: BorrowedFd<'_>,
src_offset: u64,
length: u64,
targets: &[DedupeTarget],
) -> nix::Result<Vec<DedupeResult>> {
let count = targets.len();
let base_size = mem::size_of::<btrfs_ioctl_same_args>();
let info_size = mem::size_of::<btrfs_ioctl_same_extent_info>();
let total_bytes = base_size + count * info_size;
let num_u64s = total_bytes.div_ceil(mem::size_of::<u64>());
let mut buf = vec![0u64; num_u64s];
unsafe {
let args_ptr = buf.as_mut_ptr().cast::<btrfs_ioctl_same_args>();
(*args_ptr).logical_offset = src_offset;
(*args_ptr).length = length;
#[allow(clippy::cast_possible_truncation)]
{
(*args_ptr).dest_count = count as u16;
}
let info_slice = (*args_ptr).info.as_mut_slice(count);
for (i, target) in targets.iter().enumerate() {
info_slice[i].fd = i64::from(target.fd.as_raw_fd());
info_slice[i].logical_offset = target.logical_offset;
}
btrfs_ioc_file_extent_same(src_fd.as_raw_fd(), &raw mut *args_ptr)?;
let info_slice = (*args_ptr).info.as_slice(count);
Ok(info_slice
.iter()
.map(|info| {
if info.status == 0 {
DedupeResult::Deduped(info.bytes_deduped)
} else if info.status == BTRFS_SAME_DATA_DIFFERS as i32 {
DedupeResult::DataDiffers
} else {
DedupeResult::Error(info.status)
}
})
.collect())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dedupe_result_deduped_debug() {
let r = DedupeResult::Deduped(4096);
assert_eq!(format!("{r:?}"), "Deduped(4096)");
}
#[test]
fn dedupe_result_data_differs_debug() {
let r = DedupeResult::DataDiffers;
assert_eq!(format!("{r:?}"), "DataDiffers");
}
#[test]
fn dedupe_result_error_debug() {
let r = DedupeResult::Error(-22);
assert_eq!(format!("{r:?}"), "Error(-22)");
}
#[test]
fn dedupe_result_equality() {
assert_eq!(DedupeResult::Deduped(100), DedupeResult::Deduped(100));
assert_ne!(DedupeResult::Deduped(100), DedupeResult::Deduped(200));
assert_eq!(DedupeResult::DataDiffers, DedupeResult::DataDiffers);
assert_ne!(DedupeResult::DataDiffers, DedupeResult::Deduped(0));
assert_eq!(DedupeResult::Error(-1), DedupeResult::Error(-1));
assert_ne!(DedupeResult::Error(-1), DedupeResult::Error(-2));
}
#[test]
fn allocation_sizing() {
let base_size = mem::size_of::<btrfs_ioctl_same_args>();
let info_size = mem::size_of::<btrfs_ioctl_same_extent_info>();
for count in [0, 1, 2, 5, 16, 255] {
let total_bytes = base_size + count * info_size;
let num_u64s = total_bytes.div_ceil(mem::size_of::<u64>());
let allocated = num_u64s * mem::size_of::<u64>();
assert!(
allocated >= total_bytes,
"count={count}: allocated {allocated} < needed {total_bytes}"
);
}
}
#[test]
fn btrfs_same_data_differs_value() {
assert_eq!(BTRFS_SAME_DATA_DIFFERS, 1);
}
}