Skip to content

Commit ec99b22

Browse files
committed
Auto merge of #50772 - nicokoch:fastcopy, r=alexcrichton
fs: copy: use copy_file_range on Linux Linux 4.5 introduced a new system call [copy_file_range](http://man7.org/linux/man-pages/man2/copy_file_range.2.html) to copy data from one file to another. This PR uses the new system call (if available). This has several advantages: 1. No need to constantly copy data from userspace to kernel space, if the buffer is small or the file is large 2. On some filesystems, like BTRFS, the kernel can leverage internal fs mechanisms for huge performance gains 3. Filesystems on the network dont need to copy data between the host and the client machine (they have to in the current read/write implementation) I have created a small library that also implements the new system call for some huge performance gains here: https://github.com/nicokoch/fastcopy Benchmark results are in the README
2 parents 524ad9b + c7d6a01 commit ec99b22

File tree

1 file changed

+88
-0
lines changed

1 file changed

+88
-0
lines changed

src/libstd/sys/unix/fs.rs

+88
Original file line numberDiff line numberDiff line change
@@ -794,6 +794,7 @@ pub fn canonicalize(p: &Path) -> io::Result<PathBuf> {
794794
Ok(PathBuf::from(OsString::from_vec(buf)))
795795
}
796796

797+
#[cfg(not(any(target_os = "linux", target_os = "android")))]
797798
pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
798799
use fs::{File, set_permissions};
799800
if !from.is_file() {
@@ -809,3 +810,90 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
809810
set_permissions(to, perm)?;
810811
Ok(ret)
811812
}
813+
814+
#[cfg(any(target_os = "linux", target_os = "android"))]
815+
pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
816+
use cmp;
817+
use fs::{File, set_permissions};
818+
use sync::atomic::{AtomicBool, Ordering};
819+
820+
// Kernel prior to 4.5 don't have copy_file_range
821+
// We store the availability in a global to avoid unneccessary syscalls
822+
static HAS_COPY_FILE_RANGE: AtomicBool = AtomicBool::new(true);
823+
824+
unsafe fn copy_file_range(
825+
fd_in: libc::c_int,
826+
off_in: *mut libc::loff_t,
827+
fd_out: libc::c_int,
828+
off_out: *mut libc::loff_t,
829+
len: libc::size_t,
830+
flags: libc::c_uint,
831+
) -> libc::c_long {
832+
libc::syscall(
833+
libc::SYS_copy_file_range,
834+
fd_in,
835+
off_in,
836+
fd_out,
837+
off_out,
838+
len,
839+
flags,
840+
)
841+
}
842+
843+
if !from.is_file() {
844+
return Err(Error::new(ErrorKind::InvalidInput,
845+
"the source path is not an existing regular file"))
846+
}
847+
848+
let mut reader = File::open(from)?;
849+
let mut writer = File::create(to)?;
850+
let (perm, len) = {
851+
let metadata = reader.metadata()?;
852+
(metadata.permissions(), metadata.size())
853+
};
854+
855+
let has_copy_file_range = HAS_COPY_FILE_RANGE.load(Ordering::Relaxed);
856+
let mut written = 0u64;
857+
while written < len {
858+
let copy_result = if has_copy_file_range {
859+
let bytes_to_copy = cmp::min(len - written, usize::max_value() as u64) as usize;
860+
let copy_result = unsafe {
861+
// We actually don't have to adjust the offsets,
862+
// because copy_file_range adjusts the file offset automatically
863+
cvt(copy_file_range(reader.as_raw_fd(),
864+
ptr::null_mut(),
865+
writer.as_raw_fd(),
866+
ptr::null_mut(),
867+
bytes_to_copy,
868+
0)
869+
)
870+
};
871+
if let Err(ref copy_err) = copy_result {
872+
if let Some(libc::ENOSYS) = copy_err.raw_os_error() {
873+
HAS_COPY_FILE_RANGE.store(false, Ordering::Relaxed);
874+
}
875+
}
876+
copy_result
877+
} else {
878+
Err(io::Error::from_raw_os_error(libc::ENOSYS))
879+
};
880+
match copy_result {
881+
Ok(ret) => written += ret as u64,
882+
Err(err) => {
883+
match err.raw_os_error() {
884+
Some(os_err) if os_err == libc::ENOSYS || os_err == libc::EXDEV => {
885+
// Either kernel is too old or the files are not mounted on the same fs.
886+
// Try again with fallback method
887+
assert_eq!(written, 0);
888+
let ret = io::copy(&mut reader, &mut writer)?;
889+
set_permissions(to, perm)?;
890+
return Ok(ret)
891+
},
892+
_ => return Err(err),
893+
}
894+
}
895+
}
896+
}
897+
set_permissions(to, perm)?;
898+
Ok(written)
899+
}

0 commit comments

Comments
 (0)