fs: use copy_file_range on linux

This commit is contained in:
Nicolas Koch
2018-05-15 15:25:09 +02:00
parent 76027ed2ad
commit 834ef9f08a

View File

@@ -761,6 +761,7 @@ pub fn canonicalize(p: &Path) -> io::Result<PathBuf> {
Ok(PathBuf::from(OsString::from_vec(buf)))
}
#[cfg(not(target_os = "linux"))]
pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
use fs::{File, set_permissions};
if !from.is_file() {
@@ -776,3 +777,69 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
set_permissions(to, perm)?;
Ok(ret)
}
#[cfg(target_os = "linux")]
pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
use fs::{File, set_permissions};
unsafe fn copy_file_range(
fd_in: libc::c_int,
off_in: *mut libc::loff_t,
fd_out: libc::c_int,
off_out: *mut libc::loff_t,
len: libc::size_t,
flags: libc::c_uint,
) -> libc::c_long {
libc::syscall(
libc::SYS_copy_file_range,
fd_in,
off_in,
fd_out,
off_out,
len,
flags,
)
}
if !from.is_file() {
return Err(Error::new(ErrorKind::InvalidInput,
"the source path is not an existing regular file"))
}
let mut reader = File::open(from)?;
let mut writer = File::create(to)?;
let (perm, len) = {
let metadata = reader.metadata()?;
(metadata.permissions(), metadata.size())
};
let mut written = 0u64;
while written < len {
let copy_result = unsafe {
cvt(copy_file_range(reader.as_raw_fd(),
ptr::null_mut(),
writer.as_raw_fd(),
ptr::null_mut(),
len as usize,
0)
)
};
match copy_result {
Ok(ret) => written += ret as u64,
Err(err) => {
match err.raw_os_error() {
Some(os_err) if os_err == libc::ENOSYS || os_err == libc::EXDEV => {
// Either kernel is too old or the files are not mounted on the same fs.
// Try again with fallback method
let ret = io::copy(&mut reader, &mut writer)?;
set_permissions(to, perm)?;
return Ok(ret)
},
_ => return Err(err),
}
}
}
}
set_permissions(to, perm)?;
Ok(written)
}