]> git.lizzy.rs Git - rust.git/commitdiff
fs: use copy_file_range on linux
authorNicolas Koch <nioko1337@gmail.com>
Tue, 15 May 2018 13:25:09 +0000 (15:25 +0200)
committerNicolas Koch <nioko1337@gmail.com>
Tue, 15 May 2018 13:25:09 +0000 (15:25 +0200)
src/libstd/sys/unix/fs.rs

index a1ca839dc18722158e5f6276a7c1238a89264c5b..b9f0f39bbe2afa03692ce9126388d66f651c3884 100644 (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)
+}