1use std::path::Path;
2use std::{fs, io};
3
4#[derive(Debug, Copy, Clone, Eq, PartialEq)]
5pub enum LinkType {
6 Hardlink,
7 Reflink,
8}
9
10pub fn reflink_or_hardlink(src: &Path, dst: &Path) -> io::Result<LinkType> {
13 match reflink(src, dst) {
15 Ok(()) => Ok(LinkType::Reflink),
16 Err(_) => {
17 fs::hard_link(src, dst)?;
19 Ok(LinkType::Hardlink)
20 }
21 }
22}
23
24pub fn reflink(src: &Path, dst: &Path) -> io::Result<()> {
26 #[cfg(target_os = "linux")]
27 {
28 reflink_linux(src, dst)
29 }
30 #[cfg(target_os = "macos")]
31 {
32 reflink_macos(src, dst)
33 }
34 #[cfg(target_os = "windows")]
35 {
36 reflink_windows(src, dst)
37 }
38 #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
39 {
40 Err(io::Error::new(
41 io::ErrorKind::Unsupported,
42 "Reflinks are not supported on this platform",
43 ))
44 }
45}
46
47#[cfg(target_os = "linux")]
48fn reflink_linux(src: &Path, dst: &Path) -> io::Result<()> {
49 use std::ffi::CString;
50 use std::os::unix::ffi::OsStrExt;
51
52 let src_c = CString::new(src.as_os_str().as_bytes())?;
53 let dst_c = CString::new(dst.as_os_str().as_bytes())?;
54
55 unsafe {
56 let src_fd = libc::open(src_c.as_ptr(), libc::O_RDONLY);
58 if src_fd == -1 {
59 return Err(io::Error::last_os_error());
60 }
61
62 let dst_fd = libc::open(dst_c.as_ptr(), libc::O_WRONLY | libc::O_CREAT | libc::O_EXCL, 0o644);
63 if dst_fd == -1 {
64 libc::close(src_fd);
65 return Err(io::Error::last_os_error());
66 }
67
68 const FICLONE: libc::c_ulong = 0x40049409;
70 let result = libc::ioctl(dst_fd, FICLONE, src_fd);
71
72 libc::close(src_fd);
73 libc::close(dst_fd);
74
75 if result == 0 {
76 Ok(())
77 } else {
78 let _ = libc::unlink(dst_c.as_ptr());
80 Err(io::Error::last_os_error())
81 }
82 }
83}
84
85#[cfg(target_os = "macos")]
86fn reflink_macos(src: &Path, dst: &Path) -> io::Result<()> {
87 use std::ffi::CString;
88 use std::os::unix::ffi::OsStrExt;
89
90 let src_c = CString::new(src.as_os_str().as_bytes())?;
91 let dst_c = CString::new(dst.as_os_str().as_bytes())?;
92
93 unsafe extern "C" {
95 fn clonefile(src: *const libc::c_char, dst: *const libc::c_char, flags: u32) -> libc::c_int;
96 }
97 unsafe {
98 let result = clonefile(src_c.as_ptr(), dst_c.as_ptr(), 0);
99 if result == 0 {
100 Ok(())
101 } else {
102 Err(io::Error::last_os_error())
103 }
104 }
105}
106
107#[cfg(target_os = "windows")]
108fn reflink_windows(src: &Path, dst: &Path) -> io::Result<()> {
109 use std::os::windows::ffi::OsStrExt;
110 use std::ptr;
111
112 let src_wide: Vec<u16> = src.as_os_str().encode_wide().chain(Some(0)).collect();
114 let dst_wide: Vec<u16> = dst.as_os_str().encode_wide().chain(Some(0)).collect();
115
116 unsafe {
117 extern "system" {
122 fn CopyFileExW(
123 lpExistingFileName: *const u16,
124 lpNewFileName: *const u16,
125 lpProgressRoutine: *const u8,
126 lpData: *const u8,
127 pbCancel: *const i32,
128 dwCopyFlags: u32,
129 ) -> i32;
130 }
131
132 const COPY_FILE_CLONE_FORCE: u32 = 0x00800000;
134
135 let result = CopyFileExW(
136 src_wide.as_ptr(),
137 dst_wide.as_ptr(),
138 ptr::null(),
139 ptr::null(),
140 ptr::null(),
141 COPY_FILE_CLONE_FORCE,
142 );
143
144 if result != 0 {
145 Ok(())
146 } else {
147 Err(io::Error::last_os_error())
148 }
149 }
150}