#![allow(unsafe_code)]
use std::ffi::{CString, c_char, c_int, c_void};
use std::io;
use std::os::unix::io::{BorrowedFd, RawFd};
use std::path::Path;
const COPYFILE_DATA: u32 = 0x0000_0008;
pub fn copy_file_with_fcopyfile(src_fd: RawFd, dst_fd: RawFd) -> io::Result<u64> {
let ret = unsafe {
fcopyfile(
src_fd,
dst_fd,
std::ptr::null_mut(),
COPYFILE_DATA,
)
};
if ret == -1 {
return Err(io::Error::last_os_error());
}
let borrowed = unsafe { BorrowedFd::borrow_raw(dst_fd) };
let stat = rustix::fs::fstat(borrowed).map_err(io::Error::from)?;
Ok(stat.st_size.try_into().unwrap_or(0))
}
const F_NOCACHE: c_int = 48;
pub fn set_nocache(fd: RawFd) -> io::Result<()> {
let ret = unsafe { fcntl(fd, F_NOCACHE, 1) };
if ret == -1 {
return Err(io::Error::last_os_error());
}
Ok(())
}
const AT_FDCWD: c_int = -2;
pub fn try_clone_file(src: &Path, dst: &Path) -> io::Result<()> {
let src_cstr = path_to_cstring(src)?;
let dst_cstr = path_to_cstring(dst)?;
let ret = unsafe {
fclonefileat(
AT_FDCWD,
src_cstr.as_ptr(),
AT_FDCWD,
dst_cstr.as_ptr(),
0,
)
};
if ret == -1 {
return Err(io::Error::last_os_error());
}
Ok(())
}
fn path_to_cstring(path: &Path) -> io::Result<CString> {
let bytes = path.as_os_str().as_encoded_bytes();
CString::new(bytes).map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidInput,
"path contains interior null byte",
)
})
}
unsafe extern "C" {
fn fcopyfile(
src_fd: c_int,
dst_fd: c_int,
state: *mut c_void,
flags: u32,
) -> c_int;
fn fclonefileat(
src_dirfd: c_int,
src: *const c_char,
dst_dirfd: c_int,
dst: *const c_char,
flags: u32,
) -> c_int;
fn fcntl(fd: c_int, cmd: c_int, arg: c_int) -> c_int;
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::{Read, Seek, Write};
use std::os::unix::io::AsRawFd;
#[test]
fn nocache_on_temp_file() {
let mut f = tempfile::tempfile().expect("tempfile");
f.write_all(b"hello").expect("write");
f.flush().expect("flush");
let result = set_nocache(f.as_raw_fd());
assert!(result.is_ok());
}
#[test]
fn fcopyfile_copies_data() {
let mut src = tempfile::tempfile().expect("tempfile src");
src.write_all(b"fcopyfile test data!").expect("write");
src.flush().expect("flush");
src.rewind().expect("rewind src");
let dst = tempfile::tempfile().expect("tempfile dst");
let bytes =
copy_file_with_fcopyfile(src.as_raw_fd(), dst.as_raw_fd()).expect("fcopyfile");
assert!(bytes >= 19, "expected at least 19 bytes, got {bytes}");
let mut dst_read = dst;
dst_read.rewind().expect("rewind");
let mut contents = String::new();
dst_read.read_to_string(&mut contents).expect("read");
assert!(contents.starts_with("fcopyfile test data!"));
}
#[test]
fn clone_file_creates_clone() {
let dir = tempfile::tempdir().expect("tempdir");
let src_path = dir.path().join("src.txt");
let dst_path = dir.path().join("dst.txt");
std::fs::write(&src_path, b"clone me").expect("write src");
let result = try_clone_file(&src_path, &dst_path);
if let Err(ref e) = result {
let kind = e.kind();
let raw = e.raw_os_error();
assert!(
kind == io::ErrorKind::Unsupported
|| kind == io::ErrorKind::CrossesDevices
|| kind == io::ErrorKind::PermissionDenied
|| kind == io::ErrorKind::InvalidInput
|| raw == Some(45), "unexpected clone error: {e} (raw={raw:?})"
);
}
}
}