1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 11:37:44 +00:00

cp: add --reflink support for macOS

Fixes #1773
This commit is contained in:
Nicolas Thery 2021-04-24 19:20:31 +02:00
parent b8e23c20c2
commit 4bf33e98a8
2 changed files with 68 additions and 8 deletions

View file

@ -1193,12 +1193,17 @@ fn copy_file(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> {
Ok(()) Ok(())
} }
///Copy the file from `source` to `dest` either using the normal `fs::copy` or the /// Copy the file from `source` to `dest` either using the normal `fs::copy` or a
///`FICLONE` ioctl if --reflink is specified and the filesystem supports it. /// copy-on-write scheme if --reflink is specified and the filesystem supports it.
fn copy_helper(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> { fn copy_helper(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> {
if options.reflink_mode != ReflinkMode::Never { if options.reflink_mode != ReflinkMode::Never {
#[cfg(not(target_os = "linux"))] #[cfg(not(any(target_os = "linux", target_os = "macos")))]
return Err("--reflink is only supported on linux".to_string().into()); return Err("--reflink is only supported on linux and macOS"
.to_string()
.into());
#[cfg(target_os = "macos")]
copy_on_write_macos(source, dest, options.reflink_mode)?;
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
copy_on_write_linux(source, dest, options.reflink_mode)?; copy_on_write_linux(source, dest, options.reflink_mode)?;
@ -1274,6 +1279,61 @@ fn copy_on_write_linux(source: &Path, dest: &Path, mode: ReflinkMode) -> CopyRes
Ok(()) Ok(())
} }
/// Copies `source` to `dest` using copy-on-write if possible.
#[cfg(target_os = "macos")]
fn copy_on_write_macos(source: &Path, dest: &Path, mode: ReflinkMode) -> CopyResult<()> {
debug_assert!(mode != ReflinkMode::Never);
// Extract paths in a form suitable to be passed to a syscall.
// The unwrap() is safe because they come from the command-line and so contain non nul
// character.
use std::os::unix::ffi::OsStrExt;
let src = CString::new(source.as_os_str().as_bytes()).unwrap();
let dst = CString::new(dest.as_os_str().as_bytes()).unwrap();
// clonefile(2) was introduced in macOS 10.12 so we cannot statically link against it
// for backward compatibility.
let clonefile = CString::new("clonefile").unwrap();
let raw_pfn = unsafe { libc::dlsym(libc::RTLD_NEXT, clonefile.as_ptr()) };
let mut error = 0;
if !raw_pfn.is_null() {
// Call clonefile(2).
// Safety: Casting a C function pointer to a rust function value is one of the few
// blessed uses of `transmute()`.
unsafe {
let pfn: extern "C" fn(
src: *const libc::c_char,
dst: *const libc::c_char,
flags: u32,
) -> libc::c_int = std::mem::transmute(raw_pfn);
error = pfn(src.as_ptr(), dst.as_ptr(), 0);
if std::io::Error::last_os_error().kind() == std::io::ErrorKind::AlreadyExists {
// clonefile(2) fails if the destination exists. Remove it and try again. Do not
// bother to check if removal worked because we're going to try to clone again.
let _ = fs::remove_file(dest);
error = pfn(src.as_ptr(), dst.as_ptr(), 0);
}
}
}
if raw_pfn.is_null() || error != 0 {
// clonefile(2) is not supported or it error'ed out (possibly because the FS does not
// support COW).
match mode {
ReflinkMode::Always => {
return Err(
format!("failed to clone {:?} from {:?}: {}", source, dest, error).into(),
)
}
ReflinkMode::Auto => fs::copy(source, dest).context(&*context_for(source, dest))?,
ReflinkMode::Never => unreachable!(),
};
}
Ok(())
}
/// Generate an error message if `target` is not the correct `target_type` /// Generate an error message if `target` is not the correct `target_type`
pub fn verify_target_type(target: &Path, target_type: &TargetType) -> CopyResult<()> { pub fn verify_target_type(target: &Path, target_type: &TargetType) -> CopyResult<()> {
match (target_type, target.is_dir()) { match (target_type, target.is_dir()) {

View file

@ -967,7 +967,7 @@ fn test_cp_one_file_system() {
} }
#[test] #[test]
#[cfg(target_os = "linux")] #[cfg(any(target_os = "linux", target_os = "macos"))]
fn test_cp_reflink_always() { fn test_cp_reflink_always() {
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
let result = ucmd let result = ucmd
@ -985,7 +985,7 @@ fn test_cp_reflink_always() {
} }
#[test] #[test]
#[cfg(target_os = "linux")] #[cfg(any(target_os = "linux", target_os = "macos"))]
fn test_cp_reflink_auto() { fn test_cp_reflink_auto() {
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
ucmd.arg("--reflink=auto") ucmd.arg("--reflink=auto")
@ -998,7 +998,7 @@ fn test_cp_reflink_auto() {
} }
#[test] #[test]
#[cfg(target_os = "linux")] #[cfg(any(target_os = "linux", target_os = "macos"))]
fn test_cp_reflink_never() { fn test_cp_reflink_never() {
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
ucmd.arg("--reflink=never") ucmd.arg("--reflink=never")
@ -1011,7 +1011,7 @@ fn test_cp_reflink_never() {
} }
#[test] #[test]
#[cfg(target_os = "linux")] #[cfg(any(target_os = "linux", target_os = "macos"))]
fn test_cp_reflink_bad() { fn test_cp_reflink_bad() {
let (_, mut ucmd) = at_and_ucmd!(); let (_, mut ucmd) = at_and_ucmd!();
let _result = ucmd let _result = ucmd