Skip to main content

btrfs_uapi/
reflink.rs

1//! # Lightweight file copy via `BTRFS_IOC_CLONE_RANGE`
2//!
3//! Reflinks a byte range from one file to another: the destination
4//! gains an extent reference to the source's data, no bytes are
5//! copied, and subsequent modifications are copy-on-write.
6//! Equivalent to the standard VFS `FICLONERANGE` ioctl (the btrfs
7//! and VFS encodings happen to share the same magic/number).
8
9use crate::raw::{btrfs_ioc_clone_range, btrfs_ioctl_clone_range_args};
10use std::os::{fd::AsRawFd, unix::io::BorrowedFd};
11
12/// Reflink a range of bytes from `src` to `dst`.
13///
14/// A `length` of zero is a sentinel meaning "from `src_offset` to
15/// end-of-source-file" — this matches the kernel's documented
16/// behaviour. The source and destination can be the same file.
17///
18/// # Errors
19///
20/// Common errors: `EINVAL` if offsets or length are not block-
21/// aligned (filesystem-specific; btrfs requires sector alignment
22/// for non-tail extents), `EXDEV` if the files live on different
23/// filesystems, `EPERM` if the destination is not writable, or
24/// `EOPNOTSUPP` if the filesystem doesn't support reflinks.
25pub fn clone_range(
26    src: BorrowedFd<'_>,
27    src_offset: u64,
28    length: u64,
29    dst: BorrowedFd<'_>,
30    dst_offset: u64,
31) -> nix::Result<()> {
32    let mut args = btrfs_ioctl_clone_range_args {
33        src_fd: i64::from(src.as_raw_fd()),
34        src_offset,
35        src_length: length,
36        dest_offset: dst_offset,
37    };
38    // SAFETY: `args` is fully initialised and lives for the duration
39    // of the ioctl. The kernel reads from it via copy_from_user; we
40    // do not read any output back.
41    unsafe { btrfs_ioc_clone_range(dst.as_raw_fd(), &raw mut args) }?;
42    Ok(())
43}
44
45#[cfg(test)]
46mod tests {
47    use super::*;
48    use std::mem::size_of;
49
50    #[test]
51    fn args_struct_size_matches_kernel() {
52        // s64 src_fd + 3 x u64 = 32 bytes — locked in by the kernel
53        // ABI; any drift here means the bindgen-generated layout is
54        // no longer compatible with the ioctl.
55        assert_eq!(size_of::<btrfs_ioctl_clone_range_args>(), 32);
56    }
57}