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 = send_fd as i64;
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(), &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(), &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: src_fd.as_raw_fd() as i64,
133 src_offset,
134 src_length,
135 dest_offset,
136 };
137
138 unsafe {
140 btrfs_ioc_clone_range(dest_fd.as_raw_fd(), &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 = &iov as *const _ 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(), &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() as *mut _,
227 iov_len: buf.len(),
228 };
229
230 let mut args: btrfs_ioctl_encoded_io_args = unsafe { std::mem::zeroed() };
231 args.iov = &iov as *const _ 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(), &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 =
290 SearchKey::for_type(raw::BTRFS_UUID_TREE_OBJECTID as u64, item_type);
291 key.min_objectid = objectid;
292 key.max_objectid = objectid;
293 key.min_offset = offset;
294 key.max_offset = offset;
295
296 let mut result: Option<u64> = None;
297
298 tree_search(fd, key, |_hdr, data| {
299 if data.len() >= 8 {
300 result = Some(u64::from_le_bytes(data[0..8].try_into().unwrap()));
301 }
302 Ok(())
303 })?;
304
305 result.ok_or(nix::errno::Errno::ENOENT)
306}
307
308#[cfg(test)]
309mod tests {
310 use super::*;
311
312 #[test]
313 fn send_flags_no_file_data() {
314 let flags = SendFlags::NO_FILE_DATA;
315 assert!(flags.contains(SendFlags::NO_FILE_DATA));
316 assert!(!flags.contains(SendFlags::COMPRESSED));
317 }
318
319 #[test]
320 fn send_flags_combine() {
321 let flags = SendFlags::NO_FILE_DATA | SendFlags::COMPRESSED;
322 assert!(flags.contains(SendFlags::NO_FILE_DATA));
323 assert!(flags.contains(SendFlags::COMPRESSED));
324 assert!(!flags.contains(SendFlags::VERSION));
325 }
326
327 #[test]
328 fn send_flags_empty() {
329 let flags = SendFlags::empty();
330 assert!(flags.is_empty());
331 assert_eq!(flags.bits(), 0);
332 }
333
334 #[test]
335 fn send_flags_debug() {
336 let flags = SendFlags::OMIT_STREAM_HEADER | SendFlags::OMIT_END_CMD;
337 let s = format!("{flags:?}");
338 assert!(s.contains("OMIT_STREAM_HEADER"), "debug: {s}");
339 assert!(s.contains("OMIT_END_CMD"), "debug: {s}");
340 }
341
342 #[test]
343 fn encoded_read_result_equality() {
344 let a = EncodedReadResult {
345 offset: 0,
346 unencoded_file_len: 4096,
347 unencoded_len: 4096,
348 unencoded_offset: 0,
349 compression: 0,
350 encryption: 0,
351 bytes_read: 4096,
352 };
353 let b = a;
354 assert_eq!(a, b);
355 }
356
357 #[test]
358 fn encoded_read_result_debug() {
359 let r = EncodedReadResult {
360 offset: 0,
361 unencoded_file_len: 4096,
362 unencoded_len: 8192,
363 unencoded_offset: 0,
364 compression: 3,
365 encryption: 0,
366 bytes_read: 1024,
367 };
368 let s = format!("{r:?}");
369 assert!(s.contains("compression: 3"), "debug: {s}");
370 assert!(s.contains("bytes_read: 1024"), "debug: {s}");
371 }
372
373 #[test]
374 fn subvolume_search_result_debug() {
375 let r = SubvolumeSearchResult { root_id: 256 };
376 let s = format!("{r:?}");
377 assert!(s.contains("256"), "debug: {s}");
378 }
379}