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::{SearchFilter, 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(
57 subvol_fd: BorrowedFd<'_>,
58 send_fd: RawFd,
59 parent_root: u64,
60 clone_sources: &mut [u64],
61 flags: SendFlags,
62 version: u32,
63) -> nix::Result<()> {
64 let mut args: btrfs_ioctl_send_args = unsafe { std::mem::zeroed() };
65 args.send_fd = i64::from(send_fd);
66 args.parent_root = parent_root;
67 args.clone_sources_count = clone_sources.len() as u64;
68 args.clone_sources = if clone_sources.is_empty() {
69 std::ptr::null_mut()
70 } else {
71 clone_sources.as_mut_ptr()
72 };
73 args.flags = flags.bits();
74 args.version = version;
75
76 unsafe {
79 btrfs_ioc_send(subvol_fd.as_raw_fd(), &raw const args)?;
80 }
81
82 Ok(())
83}
84
85#[derive(Debug, Clone)]
87pub struct SubvolumeSearchResult {
88 pub root_id: u64,
90}
91
92#[allow(clippy::cast_possible_wrap)] pub fn received_subvol_set(
103 fd: BorrowedFd<'_>,
104 uuid: &Uuid,
105 stransid: u64,
106) -> nix::Result<u64> {
107 let mut args: btrfs_ioctl_received_subvol_args =
108 unsafe { std::mem::zeroed() };
109
110 let uuid_bytes = uuid.as_bytes();
111 for (i, &b) in uuid_bytes.iter().enumerate() {
113 args.uuid[i] = b as std::os::raw::c_char;
114 }
115 args.stransid = stransid;
116
117 unsafe {
119 btrfs_ioc_set_received_subvol(fd.as_raw_fd(), &raw mut args)?;
120 }
121
122 Ok(args.rtransid)
123}
124
125pub fn clone_range(
138 dest_fd: BorrowedFd<'_>,
139 src_fd: BorrowedFd<'_>,
140 src_offset: u64,
141 src_length: u64,
142 dest_offset: u64,
143) -> nix::Result<()> {
144 let args = btrfs_ioctl_clone_range_args {
145 src_fd: i64::from(src_fd.as_raw_fd()),
146 src_offset,
147 src_length,
148 dest_offset,
149 };
150
151 unsafe {
153 btrfs_ioc_clone_range(dest_fd.as_raw_fd(), &raw const args)?;
154 }
155
156 Ok(())
157}
158
159#[allow(clippy::too_many_arguments)]
173#[allow(clippy::cast_possible_wrap)] pub fn encoded_write(
175 fd: BorrowedFd<'_>,
176 data: &[u8],
177 offset: u64,
178 unencoded_file_len: u64,
179 unencoded_len: u64,
180 unencoded_offset: u64,
181 compression: u32,
182 encryption: u32,
183) -> nix::Result<()> {
184 let iov = nix::libc::iovec {
185 iov_base: data.as_ptr() as *mut _,
186 iov_len: data.len(),
187 };
188
189 let mut args: btrfs_ioctl_encoded_io_args = unsafe { std::mem::zeroed() };
190 args.iov = std::ptr::from_ref(&iov) as *mut _;
191 args.iovcnt = 1;
192 args.offset = offset as i64;
193 args.len = unencoded_file_len;
194 args.unencoded_len = unencoded_len;
195 args.unencoded_offset = unencoded_offset;
196 args.compression = compression;
197 args.encryption = encryption;
198
199 unsafe {
203 btrfs_ioc_encoded_write(fd.as_raw_fd(), &raw const args)?;
204 }
205
206 Ok(())
207}
208
209#[derive(Debug, Clone, Copy, PartialEq, Eq)]
211pub struct EncodedReadResult {
212 pub offset: u64,
214 pub unencoded_file_len: u64,
216 pub unencoded_len: u64,
218 pub unencoded_offset: u64,
220 pub compression: u32,
222 pub encryption: u32,
224 pub bytes_read: usize,
226}
227
228#[allow(clippy::cast_possible_wrap)] #[allow(clippy::cast_sign_loss)] pub fn encoded_read(
244 fd: BorrowedFd<'_>,
245 buf: &mut [u8],
246 offset: u64,
247 len: u64,
248) -> nix::Result<EncodedReadResult> {
249 let iov = nix::libc::iovec {
250 iov_base: buf.as_mut_ptr().cast(),
251 iov_len: buf.len(),
252 };
253
254 let mut args: btrfs_ioctl_encoded_io_args = unsafe { std::mem::zeroed() };
255 args.iov = std::ptr::from_ref(&iov) as *mut _;
256 args.iovcnt = 1;
257 args.offset = offset as i64;
258 args.len = len;
259
260 let ret = unsafe { btrfs_ioc_encoded_read(fd.as_raw_fd(), &raw mut args) }?;
264
265 Ok(EncodedReadResult {
266 offset: args.offset as u64,
267 unencoded_file_len: args.len,
268 unencoded_len: args.unencoded_len,
269 unencoded_offset: args.unencoded_offset,
270 compression: args.compression,
271 encryption: args.encryption,
272 bytes_read: ret as usize,
273 })
274}
275
276pub fn subvolume_search_by_uuid(
285 fd: BorrowedFd<'_>,
286 uuid: &Uuid,
287) -> nix::Result<u64> {
288 search_uuid_tree(fd, uuid, raw::BTRFS_UUID_KEY_SUBVOL)
289}
290
291pub fn subvolume_search_by_received_uuid(
300 fd: BorrowedFd<'_>,
301 uuid: &Uuid,
302) -> nix::Result<u64> {
303 search_uuid_tree(fd, uuid, raw::BTRFS_UUID_KEY_RECEIVED_SUBVOL)
304}
305
306fn search_uuid_tree(
313 fd: BorrowedFd<'_>,
314 uuid: &Uuid,
315 item_type: u32,
316) -> nix::Result<u64> {
317 let bytes = uuid.as_bytes();
318 let objectid = u64::from_le_bytes(bytes[0..8].try_into().unwrap());
319 let offset = u64::from_le_bytes(bytes[8..16].try_into().unwrap());
320
321 let mut key = SearchFilter::for_type(
322 u64::from(raw::BTRFS_UUID_TREE_OBJECTID),
323 item_type,
324 );
325 key.start.objectid = objectid;
326 key.end.objectid = objectid;
327 key.start.offset = offset;
328 key.end.offset = offset;
329
330 let mut result: Option<u64> = None;
331
332 tree_search(fd, key, |_hdr, data| {
333 if data.len() >= 8 {
334 result = Some(u64::from_le_bytes(data[0..8].try_into().unwrap()));
335 }
336 Ok(())
337 })?;
338
339 result.ok_or(nix::errno::Errno::ENOENT)
340}
341
342#[cfg(test)]
343mod tests {
344 use super::*;
345
346 #[test]
347 fn send_flags_no_file_data() {
348 let flags = SendFlags::NO_FILE_DATA;
349 assert!(flags.contains(SendFlags::NO_FILE_DATA));
350 assert!(!flags.contains(SendFlags::COMPRESSED));
351 }
352
353 #[test]
354 fn send_flags_combine() {
355 let flags = SendFlags::NO_FILE_DATA | SendFlags::COMPRESSED;
356 assert!(flags.contains(SendFlags::NO_FILE_DATA));
357 assert!(flags.contains(SendFlags::COMPRESSED));
358 assert!(!flags.contains(SendFlags::VERSION));
359 }
360
361 #[test]
362 fn send_flags_empty() {
363 let flags = SendFlags::empty();
364 assert!(flags.is_empty());
365 assert_eq!(flags.bits(), 0);
366 }
367
368 #[test]
369 fn send_flags_debug() {
370 let flags = SendFlags::OMIT_STREAM_HEADER | SendFlags::OMIT_END_CMD;
371 let s = format!("{flags:?}");
372 assert!(s.contains("OMIT_STREAM_HEADER"), "debug: {s}");
373 assert!(s.contains("OMIT_END_CMD"), "debug: {s}");
374 }
375
376 #[test]
377 fn encoded_read_result_equality() {
378 let a = EncodedReadResult {
379 offset: 0,
380 unencoded_file_len: 4096,
381 unencoded_len: 4096,
382 unencoded_offset: 0,
383 compression: 0,
384 encryption: 0,
385 bytes_read: 4096,
386 };
387 let b = a;
388 assert_eq!(a, b);
389 }
390
391 #[test]
392 fn encoded_read_result_debug() {
393 let r = EncodedReadResult {
394 offset: 0,
395 unencoded_file_len: 4096,
396 unencoded_len: 8192,
397 unencoded_offset: 0,
398 compression: 3,
399 encryption: 0,
400 bytes_read: 1024,
401 };
402 let s = format!("{r:?}");
403 assert!(s.contains("compression: 3"), "debug: {s}");
404 assert!(s.contains("bytes_read: 1024"), "debug: {s}");
405 }
406
407 #[test]
408 fn subvolume_search_result_debug() {
409 let r = SubvolumeSearchResult { root_id: 256 };
410 let s = format!("{r:?}");
411 assert!(s.contains("256"), "debug: {s}");
412 }
413}