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 nix::libc::c_int;
24use std::os::fd::{AsRawFd, BorrowedFd, RawFd};
25use uuid::Uuid;
26
27bitflags! {
28 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
30 pub struct SendFlags: u64 {
31 const NO_FILE_DATA = raw::BTRFS_SEND_FLAG_NO_FILE_DATA as u64;
33 const OMIT_STREAM_HEADER = raw::BTRFS_SEND_FLAG_OMIT_STREAM_HEADER as u64;
35 const OMIT_END_CMD = raw::BTRFS_SEND_FLAG_OMIT_END_CMD as u64;
37 const VERSION = raw::BTRFS_SEND_FLAG_VERSION as u64;
39 const COMPRESSED = raw::BTRFS_SEND_FLAG_COMPRESSED as u64;
41 }
42}
43
44pub fn send(
54 subvol_fd: BorrowedFd<'_>,
55 send_fd: RawFd,
56 parent_root: u64,
57 clone_sources: &mut [u64],
58 flags: SendFlags,
59 version: u32,
60) -> nix::Result<()> {
61 let mut args: btrfs_ioctl_send_args = unsafe { std::mem::zeroed() };
62 args.send_fd = send_fd as i64;
63 args.parent_root = parent_root;
64 args.clone_sources_count = clone_sources.len() as u64;
65 args.clone_sources = if clone_sources.is_empty() {
66 std::ptr::null_mut()
67 } else {
68 clone_sources.as_mut_ptr()
69 };
70 args.flags = flags.bits();
71 args.version = version;
72
73 unsafe {
76 btrfs_ioc_send(subvol_fd.as_raw_fd() as c_int, &args)?;
77 }
78
79 Ok(())
80}
81
82#[derive(Debug, Clone)]
84pub struct SubvolumeSearchResult {
85 pub root_id: u64,
87}
88
89pub fn received_subvol_set(
95 fd: BorrowedFd<'_>,
96 uuid: &Uuid,
97 stransid: u64,
98) -> nix::Result<u64> {
99 let mut args: btrfs_ioctl_received_subvol_args =
100 unsafe { std::mem::zeroed() };
101
102 let uuid_bytes = uuid.as_bytes();
103 for (i, &b) in uuid_bytes.iter().enumerate() {
105 args.uuid[i] = b as std::os::raw::c_char;
106 }
107 args.stransid = stransid;
108
109 unsafe {
111 btrfs_ioc_set_received_subvol(fd.as_raw_fd() as c_int, &mut args)?;
112 }
113
114 Ok(args.rtransid)
115}
116
117pub fn clone_range(
126 dest_fd: BorrowedFd<'_>,
127 src_fd: BorrowedFd<'_>,
128 src_offset: u64,
129 src_length: u64,
130 dest_offset: u64,
131) -> nix::Result<()> {
132 let args = btrfs_ioctl_clone_range_args {
133 src_fd: src_fd.as_raw_fd() as i64,
134 src_offset,
135 src_length,
136 dest_offset,
137 };
138
139 unsafe {
141 btrfs_ioc_clone_range(dest_fd.as_raw_fd() as c_int, &args)?;
142 }
143
144 Ok(())
145}
146
147#[allow(clippy::too_many_arguments)]
157pub fn encoded_write(
158 fd: BorrowedFd<'_>,
159 data: &[u8],
160 offset: u64,
161 unencoded_file_len: u64,
162 unencoded_len: u64,
163 unencoded_offset: u64,
164 compression: u32,
165 encryption: u32,
166) -> nix::Result<()> {
167 let iov = nix::libc::iovec {
168 iov_base: data.as_ptr() as *mut _,
169 iov_len: data.len(),
170 };
171
172 let mut args: btrfs_ioctl_encoded_io_args = unsafe { std::mem::zeroed() };
173 args.iov = &iov as *const _ as *mut _;
174 args.iovcnt = 1;
175 args.offset = offset as i64;
176 args.len = unencoded_file_len;
177 args.unencoded_len = unencoded_len;
178 args.unencoded_offset = unencoded_offset;
179 args.compression = compression;
180 args.encryption = encryption;
181
182 unsafe {
186 btrfs_ioc_encoded_write(fd.as_raw_fd() as c_int, &args)?;
187 }
188
189 Ok(())
190}
191
192#[derive(Debug, Clone, Copy, PartialEq, Eq)]
194pub struct EncodedReadResult {
195 pub offset: u64,
197 pub unencoded_file_len: u64,
199 pub unencoded_len: u64,
201 pub unencoded_offset: u64,
203 pub compression: u32,
205 pub encryption: u32,
207 pub bytes_read: usize,
209}
210
211pub fn encoded_read(
221 fd: BorrowedFd<'_>,
222 buf: &mut [u8],
223 offset: u64,
224 len: u64,
225) -> nix::Result<EncodedReadResult> {
226 let iov = nix::libc::iovec {
227 iov_base: buf.as_mut_ptr() as *mut _,
228 iov_len: buf.len(),
229 };
230
231 let mut args: btrfs_ioctl_encoded_io_args = unsafe { std::mem::zeroed() };
232 args.iov = &iov as *const _ as *mut _;
233 args.iovcnt = 1;
234 args.offset = offset as i64;
235 args.len = len;
236
237 let ret =
241 unsafe { btrfs_ioc_encoded_read(fd.as_raw_fd() as c_int, &mut args) }?;
242
243 Ok(EncodedReadResult {
244 offset: args.offset as u64,
245 unencoded_file_len: args.len,
246 unencoded_len: args.unencoded_len,
247 unencoded_offset: args.unencoded_offset,
248 compression: args.compression,
249 encryption: args.encryption,
250 bytes_read: ret as usize,
251 })
252}
253
254pub fn subvolume_search_by_uuid(
259 fd: BorrowedFd<'_>,
260 uuid: &Uuid,
261) -> nix::Result<u64> {
262 search_uuid_tree(fd, uuid, raw::BTRFS_UUID_KEY_SUBVOL)
263}
264
265pub fn subvolume_search_by_received_uuid(
270 fd: BorrowedFd<'_>,
271 uuid: &Uuid,
272) -> nix::Result<u64> {
273 search_uuid_tree(fd, uuid, raw::BTRFS_UUID_KEY_RECEIVED_SUBVOL)
274}
275
276fn search_uuid_tree(
283 fd: BorrowedFd<'_>,
284 uuid: &Uuid,
285 item_type: u32,
286) -> nix::Result<u64> {
287 let bytes = uuid.as_bytes();
288 let objectid = u64::from_le_bytes(bytes[0..8].try_into().unwrap());
289 let offset = u64::from_le_bytes(bytes[8..16].try_into().unwrap());
290
291 let mut key =
292 SearchKey::for_type(raw::BTRFS_UUID_TREE_OBJECTID as u64, item_type);
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}