use std::ffi::CString;
use std::fs::{self, File, OpenOptions};
use std::os::unix::ffi::OsStrExt;
use std::os::unix::fs::OpenOptionsExt;
use std::path::Path;
use quick_error::ResultExt;
use uucore::buf_copy;
use uucore::mode::get_umask;
use crate::{CopyDebug, CopyResult, OffloadReflinkDebug, ReflinkMode, SparseDebug, SparseMode};
pub(crate) fn copy_on_write(
source: &Path,
dest: &Path,
reflink_mode: ReflinkMode,
sparse_mode: SparseMode,
context: &str,
source_is_fifo: bool,
source_is_stream: bool,
) -> CopyResult<CopyDebug> {
if sparse_mode != SparseMode::Auto {
return Err("--sparse is only supported on linux".to_string().into());
}
let mut copy_debug = CopyDebug {
offload: OffloadReflinkDebug::Unknown,
reflink: OffloadReflinkDebug::Unsupported,
sparse_detection: SparseDebug::Unsupported,
};
let src = CString::new(source.as_os_str().as_bytes()).unwrap();
let dst = CString::new(dest.as_os_str().as_bytes()).unwrap();
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() {
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
&& source != dest
{
if fs::metadata(dest).is_ok_and(|md| !md.permissions().readonly()) {
let _ = fs::remove_file(dest);
error = pfn(src.as_ptr(), dst.as_ptr(), 0);
}
}
}
}
if raw_pfn.is_null() || error != 0 {
match reflink_mode {
ReflinkMode::Always => {
return Err(format!("failed to clone {source:?} from {dest:?}: {error}").into())
}
_ => {
copy_debug.reflink = OffloadReflinkDebug::Yes;
if source_is_stream {
let mut src_file = File::open(source)?;
let mode = 0o622 & !get_umask();
let mut dst_file = OpenOptions::new()
.create(true)
.write(true)
.mode(mode)
.open(dest)?;
let context = buf_copy::copy_stream(&mut src_file, &mut dst_file)
.map_err(|_| std::io::Error::from(std::io::ErrorKind::Other))
.context(context)?;
if source_is_fifo {
dst_file.set_permissions(src_file.metadata()?.permissions())?;
}
context
} else {
fs::copy(source, dest).context(context)?
}
}
};
}
Ok(copy_debug)
}