summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2018-05-29 23:49:11 +0000
committerbors <bors@rust-lang.org>2018-05-29 23:49:11 +0000
commitec99b220fe0b0741edd6c9d6e05e4d0d2733a467 (patch)
tree94c87b32529ef575111261074cd3207f2e0f6cbf
parentAuto merge of #51165 - SimonSapin:opt2, r=alexcrichton (diff)
parentFix additional nits: (diff)
downloadgrust-ec99b220fe0b0741edd6c9d6e05e4d0d2733a467.tar.gz
grust-ec99b220fe0b0741edd6c9d6e05e4d0d2733a467.tar.bz2
grust-ec99b220fe0b0741edd6c9d6e05e4d0d2733a467.tar.xz
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
-rw-r--r--src/libstd/sys/unix/fs.rs88
1 files changed, 88 insertions, 0 deletions
diff --git a/src/libstd/sys/unix/fs.rs b/src/libstd/sys/unix/fs.rs
index 77968ffded..889d21cad6 100644
--- a/src/libstd/sys/unix/fs.rs
+++ b/src/libstd/sys/unix/fs.rs
@@ -794,6 +794,7 @@ pub fn canonicalize(p: &Path) -> io::Result<PathBuf> {
794 Ok(PathBuf::from(OsString::from_vec(buf))) 794 Ok(PathBuf::from(OsString::from_vec(buf)))
795} 795}
796 796
797#[cfg(not(any(target_os = "linux", target_os = "android")))]
797pub fn copy(from: &Path, to: &Path) -> io::Result<u64> { 798pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
798 use fs::{File, set_permissions}; 799 use fs::{File, set_permissions};
799 if !from.is_file() { 800 if !from.is_file() {
@@ -809,3 +810,90 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
809 set_permissions(to, perm)?; 810 set_permissions(to, perm)?;
810 Ok(ret) 811 Ok(ret)
811} 812}
813
814#[cfg(any(target_os = "linux", target_os = "android"))]
815pub 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}