Skip to content

Commit af8229d

Browse files
borsgitbot
authored and
gitbot
committed
Auto merge of rust-lang#134547 - SUPERCILEX:unify-copy, r=thomcc
Unify fs::copy and io::copy on Linux Currently, `fs::copy` first tries a regular file copy (via copy_file_range) and then falls back to userspace read/write copying. We should use `io::copy` instead as it tries copy_file_range, sendfile, and splice before falling back to userspace copying. This was discovered here: SUPERCILEX/fuc#40 Perf impact: `fs::copy` will now have two additional statx calls to decide which syscall to use. I wonder if we should get rid of the statx calls and only continue down the next fallback when the relevant syscalls say the FD isn't supported.
2 parents 2ba4093 + 5c0677a commit af8229d

File tree

2 files changed

+70
-24
lines changed

2 files changed

+70
-24
lines changed

std/src/sys/pal/unix/fs.rs

+56-23
Original file line numberDiff line numberDiff line change
@@ -1948,7 +1948,7 @@ fn open_from(from: &Path) -> io::Result<(crate::fs::File, crate::fs::Metadata)>
19481948
#[cfg(target_os = "espidf")]
19491949
fn open_to_and_set_permissions(
19501950
to: &Path,
1951-
_reader_metadata: crate::fs::Metadata,
1951+
_reader_metadata: &crate::fs::Metadata,
19521952
) -> io::Result<(crate::fs::File, crate::fs::Metadata)> {
19531953
use crate::fs::OpenOptions;
19541954
let writer = OpenOptions::new().open(to)?;
@@ -1959,7 +1959,7 @@ fn open_to_and_set_permissions(
19591959
#[cfg(not(target_os = "espidf"))]
19601960
fn open_to_and_set_permissions(
19611961
to: &Path,
1962-
reader_metadata: crate::fs::Metadata,
1962+
reader_metadata: &crate::fs::Metadata,
19631963
) -> io::Result<(crate::fs::File, crate::fs::Metadata)> {
19641964
use crate::fs::OpenOptions;
19651965
use crate::os::unix::fs::{OpenOptionsExt, PermissionsExt};
@@ -1984,30 +1984,63 @@ fn open_to_and_set_permissions(
19841984
Ok((writer, writer_metadata))
19851985
}
19861986

1987-
#[cfg(not(any(target_os = "linux", target_os = "android", target_vendor = "apple")))]
1988-
pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
1989-
let (mut reader, reader_metadata) = open_from(from)?;
1990-
let (mut writer, _) = open_to_and_set_permissions(to, reader_metadata)?;
1987+
mod cfm {
1988+
use crate::fs::{File, Metadata};
1989+
use crate::io::{BorrowedCursor, IoSlice, IoSliceMut, Read, Result, Write};
19911990

1992-
io::copy(&mut reader, &mut writer)
1993-
}
1991+
#[allow(dead_code)]
1992+
pub struct CachedFileMetadata(pub File, pub Metadata);
19941993

1994+
impl Read for CachedFileMetadata {
1995+
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
1996+
self.0.read(buf)
1997+
}
1998+
fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> Result<usize> {
1999+
self.0.read_vectored(bufs)
2000+
}
2001+
fn read_buf(&mut self, cursor: BorrowedCursor<'_>) -> Result<()> {
2002+
self.0.read_buf(cursor)
2003+
}
2004+
#[inline]
2005+
fn is_read_vectored(&self) -> bool {
2006+
self.0.is_read_vectored()
2007+
}
2008+
fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize> {
2009+
self.0.read_to_end(buf)
2010+
}
2011+
fn read_to_string(&mut self, buf: &mut String) -> Result<usize> {
2012+
self.0.read_to_string(buf)
2013+
}
2014+
}
2015+
impl Write for CachedFileMetadata {
2016+
fn write(&mut self, buf: &[u8]) -> Result<usize> {
2017+
self.0.write(buf)
2018+
}
2019+
fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> Result<usize> {
2020+
self.0.write_vectored(bufs)
2021+
}
2022+
#[inline]
2023+
fn is_write_vectored(&self) -> bool {
2024+
self.0.is_write_vectored()
2025+
}
2026+
#[inline]
2027+
fn flush(&mut self) -> Result<()> {
2028+
self.0.flush()
2029+
}
2030+
}
2031+
}
19952032
#[cfg(any(target_os = "linux", target_os = "android"))]
2033+
pub(crate) use cfm::CachedFileMetadata;
2034+
2035+
#[cfg(not(target_vendor = "apple"))]
19962036
pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
1997-
let (mut reader, reader_metadata) = open_from(from)?;
1998-
let max_len = u64::MAX;
1999-
let (mut writer, _) = open_to_and_set_permissions(to, reader_metadata)?;
2000-
2001-
use super::kernel_copy::{CopyResult, copy_regular_files};
2002-
2003-
match copy_regular_files(reader.as_raw_fd(), writer.as_raw_fd(), max_len) {
2004-
CopyResult::Ended(bytes) => Ok(bytes),
2005-
CopyResult::Error(e, _) => Err(e),
2006-
CopyResult::Fallback(written) => match io::copy::generic_copy(&mut reader, &mut writer) {
2007-
Ok(bytes) => Ok(bytes + written),
2008-
Err(e) => Err(e),
2009-
},
2010-
}
2037+
let (reader, reader_metadata) = open_from(from)?;
2038+
let (writer, writer_metadata) = open_to_and_set_permissions(to, &reader_metadata)?;
2039+
2040+
io::copy(
2041+
&mut cfm::CachedFileMetadata(reader, reader_metadata),
2042+
&mut cfm::CachedFileMetadata(writer, writer_metadata),
2043+
)
20112044
}
20122045

20132046
#[cfg(target_vendor = "apple")]
@@ -2044,7 +2077,7 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
20442077
}
20452078

20462079
// Fall back to using `fcopyfile` if `fclonefileat` does not succeed.
2047-
let (writer, writer_metadata) = open_to_and_set_permissions(to, reader_metadata)?;
2080+
let (writer, writer_metadata) = open_to_and_set_permissions(to, &reader_metadata)?;
20482081

20492082
// We ensure that `FreeOnDrop` never contains a null pointer so it is
20502083
// always safe to call `copyfile_state_free`

std/src/sys/pal/unix/kernel_copy.rs

+14-1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ use crate::process::{ChildStderr, ChildStdin, ChildStdout};
6565
use crate::ptr;
6666
use crate::sync::atomic::{AtomicBool, AtomicU8, Ordering};
6767
use crate::sys::cvt;
68+
use crate::sys::fs::CachedFileMetadata;
6869
use crate::sys::weak::syscall;
6970

7071
#[cfg(test)]
@@ -192,7 +193,7 @@ impl<R: CopyRead, W: CopyWrite> SpecCopy for Copier<'_, '_, R, W> {
192193
let w_cfg = writer.properties();
193194

194195
// before direct operations on file descriptors ensure that all source and sink buffers are empty
195-
let mut flush = || -> crate::io::Result<u64> {
196+
let mut flush = || -> Result<u64> {
196197
let bytes = reader.drain_to(writer, u64::MAX)?;
197198
// BufWriter buffered bytes have already been accounted for in earlier write() calls
198199
writer.flush()?;
@@ -537,6 +538,18 @@ impl<T: ?Sized + CopyWrite> CopyWrite for BufWriter<T> {
537538
}
538539
}
539540

541+
impl CopyRead for CachedFileMetadata {
542+
fn properties(&self) -> CopyParams {
543+
CopyParams(FdMeta::Metadata(self.1.clone()), Some(self.0.as_raw_fd()))
544+
}
545+
}
546+
547+
impl CopyWrite for CachedFileMetadata {
548+
fn properties(&self) -> CopyParams {
549+
CopyParams(FdMeta::Metadata(self.1.clone()), Some(self.0.as_raw_fd()))
550+
}
551+
}
552+
540553
fn fd_to_meta<T: AsRawFd>(fd: &T) -> FdMeta {
541554
let fd = fd.as_raw_fd();
542555
let file: ManuallyDrop<File> = ManuallyDrop::new(unsafe { File::from_raw_fd(fd) });

0 commit comments

Comments
 (0)