about summary refs log tree commit diff
path: root/src/libstd/sys
diff options
context:
space:
mode:
authorNicolas Koch <nioko1337@gmail.com>2018-05-15 15:25:09 +0200
committerNicolas Koch <nioko1337@gmail.com>2018-05-15 15:25:09 +0200
commit834ef9f08ae3429a05dead80237bb4bd04769895 (patch)
tree8df8fc109050c67cda769de3cdfc03ce761b1b4b /src/libstd/sys
parent76027ed2ad16cbb57919fe9c4dd3849469650570 (diff)
downloadrust-834ef9f08ae3429a05dead80237bb4bd04769895.tar.gz
rust-834ef9f08ae3429a05dead80237bb4bd04769895.zip
fs: use copy_file_range on linux
Diffstat (limited to 'src/libstd/sys')
-rw-r--r--src/libstd/sys/unix/fs.rs67
1 files changed, 67 insertions, 0 deletions
diff --git a/src/libstd/sys/unix/fs.rs b/src/libstd/sys/unix/fs.rs
index a1ca839dc18..b9f0f39bbe2 100644
--- a/src/libstd/sys/unix/fs.rs
+++ b/src/libstd/sys/unix/fs.rs
@@ -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)
+}