Skip to main content

btrfs_uapi/
receive.rs

1//! # Receive support: ioctls for applying a send stream to a filesystem
2//!
3//! Provides safe wrappers for the kernel interfaces used when receiving a btrfs
4//! send stream: marking a subvolume as received (`SET_RECEIVED_SUBVOL`), cloning
5//! extents between files (`CLONE_RANGE`), and searching the UUID tree to locate
6//! subvolumes by their UUID or received UUID.
7
8use crate::{
9    raw::{
10        self, btrfs_ioc_clone_range, btrfs_ioc_encoded_write, btrfs_ioc_set_received_subvol,
11        btrfs_ioctl_clone_range_args, btrfs_ioctl_encoded_io_args,
12        btrfs_ioctl_received_subvol_args,
13    },
14    tree_search::{SearchKey, tree_search},
15};
16use nix::libc::c_int;
17use std::os::fd::{AsRawFd, BorrowedFd};
18use uuid::Uuid;
19
20/// Result of searching the UUID tree for a subvolume.
21#[derive(Debug, Clone)]
22pub struct SubvolSearchResult {
23    /// The root ID (subvolume ID) found in the UUID tree.
24    pub root_id: u64,
25}
26
27/// Mark a subvolume as received by setting its received UUID and stransid.
28///
29/// After applying a send stream, this ioctl records the sender's UUID and
30/// transaction ID so that future incremental sends can use this subvolume as
31/// a reference. Returns the receive transaction ID assigned by the kernel.
32pub fn received_subvol_set(fd: BorrowedFd<'_>, uuid: &Uuid, stransid: u64) -> nix::Result<u64> {
33    let mut args: btrfs_ioctl_received_subvol_args = unsafe { std::mem::zeroed() };
34
35    let uuid_bytes = uuid.as_bytes();
36    // uuid field is [c_char; 16]; copy byte-by-byte.
37    for (i, &b) in uuid_bytes.iter().enumerate() {
38        args.uuid[i] = b as std::os::raw::c_char;
39    }
40    args.stransid = stransid;
41
42    // SAFETY: args is fully initialized, fd is a valid borrowed fd to a subvolume.
43    unsafe {
44        btrfs_ioc_set_received_subvol(fd.as_raw_fd() as c_int, &mut args)?;
45    }
46
47    Ok(args.rtransid)
48}
49
50/// Clone a range of bytes from one file to another using `BTRFS_IOC_CLONE_RANGE`.
51///
52/// Both files must be on the same btrfs filesystem. The destination file
53/// descriptor `dest_fd` is the ioctl target.
54pub fn clone_range(
55    dest_fd: BorrowedFd<'_>,
56    src_fd: BorrowedFd<'_>,
57    src_offset: u64,
58    src_length: u64,
59    dest_offset: u64,
60) -> nix::Result<()> {
61    let args = btrfs_ioctl_clone_range_args {
62        src_fd: src_fd.as_raw_fd() as i64,
63        src_offset,
64        src_length,
65        dest_offset,
66    };
67
68    // SAFETY: args is fully initialized, both fds are valid.
69    unsafe {
70        btrfs_ioc_clone_range(dest_fd.as_raw_fd() as c_int, &args)?;
71    }
72
73    Ok(())
74}
75
76/// Write pre-compressed data to a file using `BTRFS_IOC_ENCODED_WRITE`.
77///
78/// This passes compressed data directly to the filesystem without
79/// decompression, which is more efficient than decompressing and writing.
80/// The kernel may reject the call with `ENOTTY` (old kernel), `EINVAL`
81/// (unsupported parameters), or `ENOSPC`; callers should fall back to
82/// manual decompression + pwrite in those cases.
83#[allow(clippy::too_many_arguments)]
84pub fn encoded_write(
85    fd: BorrowedFd<'_>,
86    data: &[u8],
87    offset: u64,
88    unencoded_file_len: u64,
89    unencoded_len: u64,
90    unencoded_offset: u64,
91    compression: u32,
92    encryption: u32,
93) -> nix::Result<()> {
94    let iov = nix::libc::iovec {
95        iov_base: data.as_ptr() as *mut _,
96        iov_len: data.len(),
97    };
98
99    let mut args: btrfs_ioctl_encoded_io_args = unsafe { std::mem::zeroed() };
100    args.iov = &iov as *const _ as *mut _;
101    args.iovcnt = 1;
102    args.offset = offset as i64;
103    args.len = unencoded_file_len;
104    args.unencoded_len = unencoded_len;
105    args.unencoded_offset = unencoded_offset;
106    args.compression = compression;
107    args.encryption = encryption;
108
109    // SAFETY: args.iov points to a stack-allocated iovec whose iov_base
110    // references `data` which outlives this call. The ioctl reads from the
111    // iov buffers and writes encoded data to the file.
112    unsafe {
113        btrfs_ioc_encoded_write(fd.as_raw_fd() as c_int, &args)?;
114    }
115
116    Ok(())
117}
118
119/// Search the UUID tree for a subvolume by its UUID.
120///
121/// Returns the root ID of the matching subvolume, or `Errno::ENOENT` if not
122/// found.
123pub fn subvolume_search_by_uuid(fd: BorrowedFd<'_>, uuid: &Uuid) -> nix::Result<u64> {
124    search_uuid_tree(fd, uuid, raw::BTRFS_UUID_KEY_SUBVOL as u32)
125}
126
127/// Search the UUID tree for a subvolume by its received UUID.
128///
129/// Returns the root ID of the matching subvolume, or `Errno::ENOENT` if not
130/// found.
131pub fn subvolume_search_by_received_uuid(fd: BorrowedFd<'_>, uuid: &Uuid) -> nix::Result<u64> {
132    search_uuid_tree(fd, uuid, raw::BTRFS_UUID_KEY_RECEIVED_SUBVOL as u32)
133}
134
135/// Internal: search the UUID tree for a given key type.
136///
137/// The UUID tree encodes UUIDs as a compound key: objectid = LE u64 from
138/// bytes [0..8], offset = LE u64 from bytes [8..16]. The item type selects
139/// whether we are looking for regular UUIDs or received UUIDs. The data
140/// payload is a single LE u64 root ID.
141fn search_uuid_tree(fd: BorrowedFd<'_>, uuid: &Uuid, item_type: u32) -> nix::Result<u64> {
142    let bytes = uuid.as_bytes();
143    let objectid = u64::from_le_bytes(bytes[0..8].try_into().unwrap());
144    let offset = u64::from_le_bytes(bytes[8..16].try_into().unwrap());
145
146    let mut key = SearchKey::for_type(raw::BTRFS_UUID_TREE_OBJECTID as u64, item_type);
147    key.min_objectid = objectid;
148    key.max_objectid = objectid;
149    key.min_offset = offset;
150    key.max_offset = offset;
151
152    let mut result: Option<u64> = None;
153
154    tree_search(fd, key, |_hdr, data| {
155        if data.len() >= 8 {
156            result = Some(u64::from_le_bytes(data[0..8].try_into().unwrap()));
157        }
158        Ok(())
159    })?;
160
161    result.ok_or(nix::errno::Errno::ENOENT)
162}