1use crate::{
14 raw::{
15 self, btrfs_ioc_clone_range, btrfs_ioc_encoded_read,
16 btrfs_ioc_encoded_write, btrfs_ioc_send, btrfs_ioc_set_received_subvol,
17 btrfs_ioctl_clone_range_args, btrfs_ioctl_encoded_io_args,
18 btrfs_ioctl_received_subvol_args, btrfs_ioctl_send_args,
19 },
20 tree_search::{SearchKey, tree_search},
21};
22use bitflags::bitflags;
23use std::os::fd::{AsRawFd, BorrowedFd, RawFd};
24use uuid::Uuid;
25
26bitflags! {
27 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
29 pub struct SendFlags: u64 {
30 const NO_FILE_DATA = raw::BTRFS_SEND_FLAG_NO_FILE_DATA as u64;
32 const OMIT_STREAM_HEADER = raw::BTRFS_SEND_FLAG_OMIT_STREAM_HEADER as u64;
34 const OMIT_END_CMD = raw::BTRFS_SEND_FLAG_OMIT_END_CMD as u64;
36 const VERSION = raw::BTRFS_SEND_FLAG_VERSION as u64;
38 const COMPRESSED = raw::BTRFS_SEND_FLAG_COMPRESSED as u64;
40 }
41}
42
43pub fn send(
53 subvol_fd: BorrowedFd<'_>,
54 send_fd: RawFd,
55 parent_root: u64,
56 clone_sources: &mut [u64],
57 flags: SendFlags,
58 version: u32,
59) -> nix::Result<()> {
60 let mut args: btrfs_ioctl_send_args = unsafe { std::mem::zeroed() };
61 args.send_fd = i64::from(send_fd);
62 args.parent_root = parent_root;
63 args.clone_sources_count = clone_sources.len() as u64;
64 args.clone_sources = if clone_sources.is_empty() {
65 std::ptr::null_mut()
66 } else {
67 clone_sources.as_mut_ptr()
68 };
69 args.flags = flags.bits();
70 args.version = version;
71
72 unsafe {
75 btrfs_ioc_send(subvol_fd.as_raw_fd(), &raw const args)?;
76 }
77
78 Ok(())
79}
80
81#[derive(Debug, Clone)]
83pub struct SubvolumeSearchResult {
84 pub root_id: u64,
86}
87
88pub fn received_subvol_set(
94 fd: BorrowedFd<'_>,
95 uuid: &Uuid,
96 stransid: u64,
97) -> nix::Result<u64> {
98 let mut args: btrfs_ioctl_received_subvol_args =
99 unsafe { std::mem::zeroed() };
100
101 let uuid_bytes = uuid.as_bytes();
102 for (i, &b) in uuid_bytes.iter().enumerate() {
104 args.uuid[i] = b as std::os::raw::c_char;
105 }
106 args.stransid = stransid;
107
108 unsafe {
110 btrfs_ioc_set_received_subvol(fd.as_raw_fd(), &raw mut args)?;
111 }
112
113 Ok(args.rtransid)
114}
115
116pub fn clone_range(
125 dest_fd: BorrowedFd<'_>,
126 src_fd: BorrowedFd<'_>,
127 src_offset: u64,
128 src_length: u64,
129 dest_offset: u64,
130) -> nix::Result<()> {
131 let args = btrfs_ioctl_clone_range_args {
132 src_fd: i64::from(src_fd.as_raw_fd()),
133 src_offset,
134 src_length,
135 dest_offset,
136 };
137
138 unsafe {
140 btrfs_ioc_clone_range(dest_fd.as_raw_fd(), &raw const args)?;
141 }
142
143 Ok(())
144}
145
146#[allow(clippy::too_many_arguments)]
156pub fn encoded_write(
157 fd: BorrowedFd<'_>,
158 data: &[u8],
159 offset: u64,
160 unencoded_file_len: u64,
161 unencoded_len: u64,
162 unencoded_offset: u64,
163 compression: u32,
164 encryption: u32,
165) -> nix::Result<()> {
166 let iov = nix::libc::iovec {
167 iov_base: data.as_ptr() as *mut _,
168 iov_len: data.len(),
169 };
170
171 let mut args: btrfs_ioctl_encoded_io_args = unsafe { std::mem::zeroed() };
172 args.iov = std::ptr::from_ref(&iov) as *mut _;
173 args.iovcnt = 1;
174 args.offset = offset as i64;
175 args.len = unencoded_file_len;
176 args.unencoded_len = unencoded_len;
177 args.unencoded_offset = unencoded_offset;
178 args.compression = compression;
179 args.encryption = encryption;
180
181 unsafe {
185 btrfs_ioc_encoded_write(fd.as_raw_fd(), &raw const args)?;
186 }
187
188 Ok(())
189}
190
191#[derive(Debug, Clone, Copy, PartialEq, Eq)]
193pub struct EncodedReadResult {
194 pub offset: u64,
196 pub unencoded_file_len: u64,
198 pub unencoded_len: u64,
200 pub unencoded_offset: u64,
202 pub compression: u32,
204 pub encryption: u32,
206 pub bytes_read: usize,
208}
209
210pub fn encoded_read(
220 fd: BorrowedFd<'_>,
221 buf: &mut [u8],
222 offset: u64,
223 len: u64,
224) -> nix::Result<EncodedReadResult> {
225 let iov = nix::libc::iovec {
226 iov_base: buf.as_mut_ptr().cast(),
227 iov_len: buf.len(),
228 };
229
230 let mut args: btrfs_ioctl_encoded_io_args = unsafe { std::mem::zeroed() };
231 args.iov = std::ptr::from_ref(&iov) as *mut _;
232 args.iovcnt = 1;
233 args.offset = offset as i64;
234 args.len = len;
235
236 let ret = unsafe { btrfs_ioc_encoded_read(fd.as_raw_fd(), &raw mut args) }?;
240
241 Ok(EncodedReadResult {
242 offset: args.offset as u64,
243 unencoded_file_len: args.len,
244 unencoded_len: args.unencoded_len,
245 unencoded_offset: args.unencoded_offset,
246 compression: args.compression,
247 encryption: args.encryption,
248 bytes_read: ret as usize,
249 })
250}
251
252pub fn subvolume_search_by_uuid(
257 fd: BorrowedFd<'_>,
258 uuid: &Uuid,
259) -> nix::Result<u64> {
260 search_uuid_tree(fd, uuid, raw::BTRFS_UUID_KEY_SUBVOL)
261}
262
263pub fn subvolume_search_by_received_uuid(
268 fd: BorrowedFd<'_>,
269 uuid: &Uuid,
270) -> nix::Result<u64> {
271 search_uuid_tree(fd, uuid, raw::BTRFS_UUID_KEY_RECEIVED_SUBVOL)
272}
273
274fn search_uuid_tree(
281 fd: BorrowedFd<'_>,
282 uuid: &Uuid,
283 item_type: u32,
284) -> nix::Result<u64> {
285 let bytes = uuid.as_bytes();
286 let objectid = u64::from_le_bytes(bytes[0..8].try_into().unwrap());
287 let offset = u64::from_le_bytes(bytes[8..16].try_into().unwrap());
288
289 let mut key = SearchKey::for_type(
290 u64::from(raw::BTRFS_UUID_TREE_OBJECTID),
291 item_type,
292 );
293 key.min_objectid = objectid;
294 key.max_objectid = objectid;
295 key.min_offset = offset;
296 key.max_offset = offset;
297
298 let mut result: Option<u64> = None;
299
300 tree_search(fd, key, |_hdr, data| {
301 if data.len() >= 8 {
302 result = Some(u64::from_le_bytes(data[0..8].try_into().unwrap()));
303 }
304 Ok(())
305 })?;
306
307 result.ok_or(nix::errno::Errno::ENOENT)
308}
309
310#[cfg(test)]
311mod tests {
312 use super::*;
313
314 #[test]
315 fn send_flags_no_file_data() {
316 let flags = SendFlags::NO_FILE_DATA;
317 assert!(flags.contains(SendFlags::NO_FILE_DATA));
318 assert!(!flags.contains(SendFlags::COMPRESSED));
319 }
320
321 #[test]
322 fn send_flags_combine() {
323 let flags = SendFlags::NO_FILE_DATA | SendFlags::COMPRESSED;
324 assert!(flags.contains(SendFlags::NO_FILE_DATA));
325 assert!(flags.contains(SendFlags::COMPRESSED));
326 assert!(!flags.contains(SendFlags::VERSION));
327 }
328
329 #[test]
330 fn send_flags_empty() {
331 let flags = SendFlags::empty();
332 assert!(flags.is_empty());
333 assert_eq!(flags.bits(), 0);
334 }
335
336 #[test]
337 fn send_flags_debug() {
338 let flags = SendFlags::OMIT_STREAM_HEADER | SendFlags::OMIT_END_CMD;
339 let s = format!("{flags:?}");
340 assert!(s.contains("OMIT_STREAM_HEADER"), "debug: {s}");
341 assert!(s.contains("OMIT_END_CMD"), "debug: {s}");
342 }
343
344 #[test]
345 fn encoded_read_result_equality() {
346 let a = EncodedReadResult {
347 offset: 0,
348 unencoded_file_len: 4096,
349 unencoded_len: 4096,
350 unencoded_offset: 0,
351 compression: 0,
352 encryption: 0,
353 bytes_read: 4096,
354 };
355 let b = a;
356 assert_eq!(a, b);
357 }
358
359 #[test]
360 fn encoded_read_result_debug() {
361 let r = EncodedReadResult {
362 offset: 0,
363 unencoded_file_len: 4096,
364 unencoded_len: 8192,
365 unencoded_offset: 0,
366 compression: 3,
367 encryption: 0,
368 bytes_read: 1024,
369 };
370 let s = format!("{r:?}");
371 assert!(s.contains("compression: 3"), "debug: {s}");
372 assert!(s.contains("bytes_read: 1024"), "debug: {s}");
373 }
374
375 #[test]
376 fn subvolume_search_result_debug() {
377 let r = SubvolumeSearchResult { root_id: 256 };
378 let s = format!("{r:?}");
379 assert!(s.contains("256"), "debug: {s}");
380 }
381}