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
37#[allow(clippy::cast_possible_wrap)] pub fn file_extent_same(
52 src_fd: BorrowedFd<'_>,
53 src_offset: u64,
54 length: u64,
55 targets: &[DedupeTarget],
56) -> nix::Result<Vec<DedupeResult>> {
57 let count = targets.len();
58
59 let base_size = mem::size_of::<btrfs_ioctl_same_args>();
61 let info_size = mem::size_of::<btrfs_ioctl_same_extent_info>();
62 let total_bytes = base_size + count * info_size;
63 let num_u64s = total_bytes.div_ceil(mem::size_of::<u64>());
64 let mut buf = vec![0u64; num_u64s];
65
66 unsafe {
70 let args_ptr = buf.as_mut_ptr().cast::<btrfs_ioctl_same_args>();
71 (*args_ptr).logical_offset = src_offset;
72 (*args_ptr).length = length;
73 #[allow(clippy::cast_possible_truncation)]
74 {
76 (*args_ptr).dest_count = count as u16;
77 }
78
79 let info_slice = (*args_ptr).info.as_mut_slice(count);
80 for (i, target) in targets.iter().enumerate() {
81 info_slice[i].fd = i64::from(target.fd.as_raw_fd());
82 info_slice[i].logical_offset = target.logical_offset;
83 }
84
85 btrfs_ioc_file_extent_same(src_fd.as_raw_fd(), &raw mut *args_ptr)?;
86
87 let info_slice = (*args_ptr).info.as_slice(count);
88 Ok(info_slice
89 .iter()
90 .map(|info| {
91 if info.status == 0 {
92 DedupeResult::Deduped(info.bytes_deduped)
93 } else if info.status == BTRFS_SAME_DATA_DIFFERS as i32 {
94 DedupeResult::DataDiffers
95 } else {
96 DedupeResult::Error(info.status)
97 }
98 })
99 .collect())
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 #[test]
108 fn dedupe_result_deduped_debug() {
109 let r = DedupeResult::Deduped(4096);
110 assert_eq!(format!("{r:?}"), "Deduped(4096)");
111 }
112
113 #[test]
114 fn dedupe_result_data_differs_debug() {
115 let r = DedupeResult::DataDiffers;
116 assert_eq!(format!("{r:?}"), "DataDiffers");
117 }
118
119 #[test]
120 fn dedupe_result_error_debug() {
121 let r = DedupeResult::Error(-22);
122 assert_eq!(format!("{r:?}"), "Error(-22)");
123 }
124
125 #[test]
126 fn dedupe_result_equality() {
127 assert_eq!(DedupeResult::Deduped(100), DedupeResult::Deduped(100));
128 assert_ne!(DedupeResult::Deduped(100), DedupeResult::Deduped(200));
129 assert_eq!(DedupeResult::DataDiffers, DedupeResult::DataDiffers);
130 assert_ne!(DedupeResult::DataDiffers, DedupeResult::Deduped(0));
131 assert_eq!(DedupeResult::Error(-1), DedupeResult::Error(-1));
132 assert_ne!(DedupeResult::Error(-1), DedupeResult::Error(-2));
133 }
134
135 #[test]
136 fn allocation_sizing() {
137 let base_size = mem::size_of::<btrfs_ioctl_same_args>();
139 let info_size = mem::size_of::<btrfs_ioctl_same_extent_info>();
140
141 for count in [0, 1, 2, 5, 16, 255] {
142 let total_bytes = base_size + count * info_size;
143 let num_u64s = total_bytes.div_ceil(mem::size_of::<u64>());
144 let allocated = num_u64s * mem::size_of::<u64>();
145 assert!(
146 allocated >= total_bytes,
147 "count={count}: allocated {allocated} < needed {total_bytes}"
148 );
149 }
150 }
151
152 #[test]
153 fn btrfs_same_data_differs_value() {
154 assert_eq!(BTRFS_SAME_DATA_DIFFERS, 1);
156 }
157}