diff options
author | bors <bors@rust-lang.org> | 2018-05-29 23:49:11 +0000 |
---|---|---|
committer | bors <bors@rust-lang.org> | 2018-05-29 23:49:11 +0000 |
commit | ec99b220fe0b0741edd6c9d6e05e4d0d2733a467 (patch) | |
tree | 94c87b32529ef575111261074cd3207f2e0f6cbf | |
parent | Auto merge of #51165 - SimonSapin:opt2, r=alexcrichton (diff) | |
parent | Fix additional nits: (diff) | |
download | grust-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.rs | 88 |
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")))] | ||
797 | pub fn copy(from: &Path, to: &Path) -> io::Result<u64> { | 798 | pub 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"))] | ||
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 | } | ||