1use crate::raw::{
9 BTRFS_SAME_DATA_DIFFERS, btrfs_ioc_file_extent_same, btrfs_ioctl_same_args,
10 btrfs_ioctl_same_extent_info,
11};
12use std::{
13 mem,
14 os::{fd::AsRawFd, unix::io::BorrowedFd},
15};
16
17#[derive(Debug, Clone)]
19pub struct DedupeTarget {
20 pub fd: BorrowedFd<'static>,
22 pub logical_offset: u64,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum DedupeResult {
29 Deduped(u64),
31 DataDiffers,
33 Error(i32),
35}
36
37pub fn file_extent_same(
49 src_fd: BorrowedFd<'_>,
50 src_offset: u64,
51 length: u64,
52 targets: &[DedupeTarget],
53) -> nix::Result<Vec<DedupeResult>> {
54 let count = targets.len();
55
56 let base_size = mem::size_of::<btrfs_ioctl_same_args>();
58 let info_size = mem::size_of::<btrfs_ioctl_same_extent_info>();
59 let total_bytes = base_size + count * info_size;
60 let num_u64s = total_bytes.div_ceil(mem::size_of::<u64>());
61 let mut buf = vec![0u64; num_u64s];
62
63 unsafe {
67 let args_ptr = buf.as_mut_ptr() as *mut btrfs_ioctl_same_args;
68 (*args_ptr).logical_offset = src_offset;
69 (*args_ptr).length = length;
70 (*args_ptr).dest_count = count as u16;
71
72 let info_slice = (*args_ptr).info.as_mut_slice(count);
73 for (i, target) in targets.iter().enumerate() {
74 info_slice[i].fd = target.fd.as_raw_fd() as i64;
75 info_slice[i].logical_offset = target.logical_offset;
76 }
77
78 btrfs_ioc_file_extent_same(
79 src_fd.as_raw_fd() as nix::libc::c_int,
80 &mut *args_ptr,
81 )?;
82
83 let info_slice = (*args_ptr).info.as_slice(count);
84 Ok(info_slice
85 .iter()
86 .map(|info| {
87 if info.status == 0 {
88 DedupeResult::Deduped(info.bytes_deduped)
89 } else if info.status == BTRFS_SAME_DATA_DIFFERS as i32 {
90 DedupeResult::DataDiffers
91 } else {
92 DedupeResult::Error(info.status)
93 }
94 })
95 .collect())
96 }
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102
103 #[test]
104 fn dedupe_result_deduped_debug() {
105 let r = DedupeResult::Deduped(4096);
106 assert_eq!(format!("{r:?}"), "Deduped(4096)");
107 }
108
109 #[test]
110 fn dedupe_result_data_differs_debug() {
111 let r = DedupeResult::DataDiffers;
112 assert_eq!(format!("{r:?}"), "DataDiffers");
113 }
114
115 #[test]
116 fn dedupe_result_error_debug() {
117 let r = DedupeResult::Error(-22);
118 assert_eq!(format!("{r:?}"), "Error(-22)");
119 }
120
121 #[test]
122 fn dedupe_result_equality() {
123 assert_eq!(DedupeResult::Deduped(100), DedupeResult::Deduped(100));
124 assert_ne!(DedupeResult::Deduped(100), DedupeResult::Deduped(200));
125 assert_eq!(DedupeResult::DataDiffers, DedupeResult::DataDiffers);
126 assert_ne!(DedupeResult::DataDiffers, DedupeResult::Deduped(0));
127 assert_eq!(DedupeResult::Error(-1), DedupeResult::Error(-1));
128 assert_ne!(DedupeResult::Error(-1), DedupeResult::Error(-2));
129 }
130
131 #[test]
132 fn allocation_sizing() {
133 let base_size = mem::size_of::<btrfs_ioctl_same_args>();
135 let info_size = mem::size_of::<btrfs_ioctl_same_extent_info>();
136
137 for count in [0, 1, 2, 5, 16, 255] {
138 let total_bytes = base_size + count * info_size;
139 let num_u64s = total_bytes.div_ceil(mem::size_of::<u64>());
140 let allocated = num_u64s * mem::size_of::<u64>();
141 assert!(
142 allocated >= total_bytes,
143 "count={count}: allocated {allocated} < needed {total_bytes}"
144 );
145 }
146 }
147
148 #[test]
149 fn btrfs_same_data_differs_value() {
150 assert_eq!(BTRFS_SAME_DATA_DIFFERS, 1);
152 }
153}