possum/sys/clonefile/
sys.rs

1#![allow(unused_imports)]
2use std::ffi::CString;
3#[cfg(target_os = "linux")]
4use std::fs::File;
5use std::io;
6use std::io::{Error, ErrorKind};
7#[cfg(unix)]
8use std::os::unix::ffi::OsStrExt;
9use std::path::Path;
10
11use libc::{ENOTSUP, EOPNOTSUPP};
12
13use super::*;
14use crate::cpathbuf::CPathBuf;
15use crate::Error::UnsupportedFilesystem;
16use crate::PubResult;
17cfg_if! {
18    if #[cfg(windows)] {
19    }
20}
21
22pub trait CloneFileError {
23    fn is_unsupported(&self) -> bool;
24}
25
26impl CloneFileError for NativeIoError {
27    #[cfg(unix)]
28    fn is_unsupported(&self) -> bool {
29        self.raw_os_error()
30            .map(|errno| matches!(errno, EOPNOTSUPP | libc::EXDEV))
31            .unwrap_or_default()
32    }
33    #[cfg(windows)]
34    fn is_unsupported(&self) -> bool {
35        // Where can I find a list of possible error codes? At least on Windows there is a file
36        // system flag that might avoid us needing to use this value.
37        false
38    }
39}
40
41#[allow(dead_code)]
42#[cfg(unix)]
43// Here and not in crate::Error because ENOTSUP has special meaning for clonefile.
44fn last_errno() -> NativeIoError {
45    io::Error::last_os_error()
46    // let errno = errno();
47    // // On Linux this is EOPNOTSUP, but on Linux it's also the same value as ENOTSUP.
48    // if errno == ENOTSUP || errno == EOPNOTSUPP {
49    //     crate::Error::UnsupportedFilesystem
50    // } else {
51    //     io::Error::from_raw_os_error(errno).into()
52    // }
53}
54
55// TODO: On Solaris we want to use reflink(3)
56
57pub fn clonefile(src_path: &Path, dst_path: &Path) -> Result<(), std::io::Error> {
58    cfg_if! {
59        if #[cfg(target_os = "macos")] {
60            let src_buf = CString::new(src_path.as_os_str().as_bytes()).unwrap();
61            let dst_buf = CString::new(dst_path.as_os_str().as_bytes()).unwrap();
62            let src = src_buf.as_ptr();
63            let dst = dst_buf.as_ptr();
64            let val = unsafe { libc::clonefile(src, dst, 0) };
65            if val != 0 {
66                return Err(last_errno());
67            }
68        } else {
69            let src_file = File::open(src_path)?;
70            fclonefile_noflags(&src_file, dst_path)?;
71        }
72    }
73    Ok(())
74}
75
76// fclonefileat but the dst is probably supposed to be an absolute path.
77#[allow(unused_variables)]
78pub fn fclonefile_noflags(src_file: &File, dst_path: &Path) -> Result<(), NativeIoError> {
79    cfg_if! {
80        if #[cfg(windows)] {
81            use std::ffi::c_void;
82            let dst_file = File::create(dst_path)?;
83            let dst_handle = std_handle_to_windows(dst_file.as_raw_handle());
84            let src_metadata = src_file.metadata()?;
85            let byte_count = src_metadata.len() as i64;
86            let data = DUPLICATE_EXTENTS_DATA {
87                FileHandle: HANDLE(src_file.as_raw_handle() as isize),
88                SourceFileOffset: 0,
89                TargetFileOffset: 0,
90                ByteCount: byte_count,
91            };
92            let data_ptr = &data as *const _ as *const c_void;
93            unsafe {
94                DeviceIoControl
95            (
96                dst_handle,
97                FSCTL_DUPLICATE_EXTENTS_TO_FILE,
98                Some(data_ptr),
99                std::mem::size_of_val(&data) as u32,
100                None,
101                0,
102                None,
103                None,
104            )}?;
105            Ok(())
106        } else if #[cfg(target_os = "linux")] {
107            // Should this be exclusive create?
108            let dst_file = File::create(dst_path)?;
109            let src_fd = src_file.as_raw_fd();
110            let dst_fd = dst_file.as_raw_fd();
111            // Is this because the musl bindings are wrong?
112            #[allow(clippy::useless_conversion)]
113            let request = libc::FICLONE.try_into().unwrap();
114            let rv = unsafe { libc::ioctl(dst_fd, request, src_fd) };
115            if rv == -1 {
116                return Err(last_errno());
117            }
118            Ok(())
119        } else if #[cfg(target_os = "freebsd")] {
120            // Looks like the syscall isn't ready yet.
121            // https://github.com/openzfs/zfs/pull/13392#issue-1221750354
122            // Should this be exclusive create?
123
124            // let dst_file = File::create(dst_path)?;
125            // let src_fd = src_file.as_raw_fd();
126            // let dst_fd = dst_file.as_raw_fd();
127            // let rv = libc::fclonefile(src_fd, dst_fd);
128            // if rv == -1 {
129            //     return Err(last_errno());
130            // }
131
132            // Screen this sooner by claiming no clonefile support before it's even attempted.
133            unimplemented!()
134        } else {
135            // assert!(dst_path.is_absolute());
136            let dst_buf = CPathBuf::try_from(dst_path).unwrap();
137            let dst = dst_buf.as_ptr();
138            let val = unsafe { libc::fclonefileat(src_file.as_raw_fd(), -1, dst, 0) };
139            if val != 0 {
140                return Err(last_errno());
141            }
142            Ok(())
143        }
144    }
145}