diff --git a/src/libstd/sys/unix/fs.rs b/src/libstd/sys/unix/fs.rs index 3b80b475a93db..38052cbde601a 100644 --- a/src/libstd/sys/unix/fs.rs +++ b/src/libstd/sys/unix/fs.rs @@ -847,7 +847,8 @@ pub fn copy(from: &Path, to: &Path) -> io::Result { #[cfg(any(target_os = "linux", target_os = "android"))] pub fn copy(from: &Path, to: &Path) -> io::Result { use crate::cmp; - use crate::fs::File; + use crate::fs::{File, OpenOptions}; + use crate::os::unix::fs::{OpenOptionsExt, PermissionsExt}; use crate::sync::atomic::{AtomicBool, Ordering}; // Kernel prior to 4.5 don't have copy_file_range @@ -873,18 +874,35 @@ pub fn copy(from: &Path, to: &Path) -> io::Result { ) } - 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()) + if !metadata.is_file() { + return Err(Error::new( + ErrorKind::InvalidInput, + "the source path is not an existing regular file", + )); + } + (metadata.permissions(), metadata.len()) }; + let mut writer = OpenOptions::new() + // create the file with the correct mode right away + .mode(perm.mode()) + .write(true) + .create(true) + .truncate(true) + .open(to)?; + + let writer_metadata = writer.metadata()?; + if writer_metadata.is_file() { + // Set the correct file permissions, in case the file already existed. + // Don't set the permissions on already existing non-files like + // pipes/FIFOs or device nodes. + writer.set_permissions(perm)?; + } + let has_copy_file_range = HAS_COPY_FILE_RANGE.load(Ordering::Relaxed); let mut written = 0u64; while written < len { @@ -919,21 +937,20 @@ pub fn copy(from: &Path, to: &Path) -> io::Result { match err.raw_os_error() { Some(os_err) if os_err == libc::ENOSYS || os_err == libc::EXDEV + || os_err == libc::EINVAL || os_err == libc::EPERM => { // Try fallback io::copy if either: // - Kernel version is < 4.5 (ENOSYS) // - Files are mounted on different fs (EXDEV) // - copy_file_range is disallowed, for example by seccomp (EPERM) + // - copy_file_range cannot be used with pipes or device nodes (EINVAL) assert_eq!(written, 0); - let ret = io::copy(&mut reader, &mut writer)?; - writer.set_permissions(perm)?; - return Ok(ret) + return io::copy(&mut reader, &mut writer); }, _ => return Err(err), } } } } - writer.set_permissions(perm)?; Ok(written) }