Skip to content

Commit 46e7fbe

Browse files
committed
reduce syscalls by inferring FD types based on source struct instead of calling stat()
also adds handling for edge-cases involving large sparse files where sendfile could fail with EOVERFLOW
1 parent 0624730 commit 46e7fbe

File tree

2 files changed

+158
-93
lines changed

2 files changed

+158
-93
lines changed

library/std/src/io/copy.rs

Lines changed: 102 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ mod kernel_copy {
9999
use crate::os::unix::fs::FileTypeExt;
100100
use crate::os::unix::io::{AsRawFd, FromRawFd, RawFd};
101101
use crate::process::{ChildStderr, ChildStdin, ChildStdout};
102+
use crate::sys::fs::{copy_regular_files, sendfile_splice, CopyResult, SpliceMode};
102103

103104
pub(super) fn copy_spec<R: Read + ?Sized, W: Write + ?Sized>(
104105
read: &mut R,
@@ -108,20 +109,55 @@ mod kernel_copy {
108109
SpecCopy::copy(copier)
109110
}
110111

112+
/// This type represents either the inferred `FileType` of a `RawFd` based on the source
113+
/// type from which it was extracted or the actual metadata
114+
///
115+
/// The methods on this type only provide hints, due to `AsRawFd` and `FromRawFd` the inferred
116+
/// type may be wrong.
111117
enum FdMeta {
118+
/// We obtained the FD from a type that can contain any type of `FileType` and queried the metadata
119+
/// because it is cheaper than probing all possible syscalls (reader side)
112120
Metadata(Metadata),
113121
Socket,
114122
Pipe,
115-
None,
123+
/// We don't have any metadata, e.g. because the original type was `File` which can represent
124+
/// any `FileType` and we did not query the metadata either since it did not seem beneficial
125+
/// (writer side)
126+
NoneObtained,
116127
}
117128

118129
impl FdMeta {
119-
fn is_fifo(&self) -> bool {
130+
fn maybe_fifo(&self) -> bool {
120131
match self {
121132
FdMeta::Metadata(meta) => meta.file_type().is_fifo(),
122133
FdMeta::Socket => false,
123134
FdMeta::Pipe => true,
124-
FdMeta::None => false,
135+
FdMeta::NoneObtained => true,
136+
}
137+
}
138+
139+
fn potential_sendfile_source(&self) -> bool {
140+
match self {
141+
// procfs erronously shows 0 length on non-empty readable files.
142+
// and if a file is truly empty then a `read` syscall will determine that and skip the write syscall
143+
// thus there would be benefit from attempting sendfile
144+
FdMeta::Metadata(meta)
145+
if meta.file_type().is_file() && meta.len() > 0
146+
|| meta.file_type().is_block_device() =>
147+
{
148+
true
149+
}
150+
_ => false,
151+
}
152+
}
153+
154+
fn copy_file_range_candidate(&self) -> bool {
155+
match self {
156+
// copy_file_range will fail on empty procfs files. `read` can determine whether EOF has been reached
157+
// without extra cost and skip the write, thus there is no benefit in attempting copy_file_range
158+
FdMeta::Metadata(meta) if meta.is_file() && meta.len() > 0 => true,
159+
FdMeta::NoneObtained => true,
160+
_ => false,
125161
}
126162
}
127163
}
@@ -149,66 +185,65 @@ mod kernel_copy {
149185
let r_cfg = reader.properties();
150186
let w_cfg = writer.properties();
151187

152-
// before direct operations on file descriptors ensure that all source and sink buffers are emtpy
188+
// before direct operations on file descriptors ensure that all source and sink buffers are emtpy
153189
let mut flush = || -> crate::io::Result<u64> {
154190
let bytes = reader.drain_to(writer, u64::MAX)?;
191+
// BufWriter buffered bytes have already been accounted for in earlier write() calls
155192
writer.flush()?;
156193
Ok(bytes)
157194
};
158195

159-
match (r_cfg, w_cfg) {
160-
(
161-
CopyParams(FdMeta::Metadata(reader_meta), Some(readfd)),
162-
CopyParams(FdMeta::Metadata(writer_meta), Some(writefd)),
163-
) if reader_meta.is_file() && writer_meta.is_file() => {
164-
let bytes_flushed = flush()?;
165-
let max_write = reader.min_limit();
166-
let (mut reader, mut writer) =
167-
unsafe { (fd_as_file(readfd), fd_as_file(writefd)) };
168-
let len = reader_meta.len();
169-
crate::sys::fs::copy_regular_files(
170-
&mut reader,
171-
&mut writer,
172-
min(len, max_write),
173-
)
174-
.map(|bytes_copied| bytes_copied + bytes_flushed)
196+
let mut written = 0u64;
197+
198+
if let (CopyParams(input_meta, Some(readfd)), CopyParams(output_meta, Some(writefd))) =
199+
(r_cfg, w_cfg)
200+
{
201+
written += flush()?;
202+
let max_write = reader.min_limit();
203+
204+
if input_meta.copy_file_range_candidate() && output_meta.copy_file_range_candidate()
205+
{
206+
let result = copy_regular_files(readfd, writefd, max_write);
207+
208+
match result {
209+
CopyResult::Ended(Ok(bytes_copied)) => return Ok(bytes_copied + written),
210+
CopyResult::Ended(err) => return err,
211+
CopyResult::Fallback(bytes) => written += bytes,
212+
}
175213
}
176-
(
177-
CopyParams(FdMeta::Metadata(reader_meta), Some(readfd)),
178-
CopyParams(_, Some(writefd)),
179-
) if reader_meta.is_file() => {
180-
// try sendfile, most modern systems it should work with any target as long as the source is a mmapable file.
181-
// in the rare cases where it's no supported the wrapper function will fall back to a normal copy loop
182-
let bytes_flushed = flush()?;
183-
let (mut reader, mut writer) =
184-
unsafe { (fd_as_file(readfd), fd_as_file(writefd)) };
185-
let len = reader_meta.len();
186-
let max_write = reader.min_limit();
187-
crate::sys::fs::sendfile_splice(
188-
crate::sys::fs::SpliceMode::Sendfile,
189-
&mut reader,
190-
&mut writer,
191-
min(len, max_write),
192-
)
193-
.map(|bytes_sent| bytes_sent + bytes_flushed)
214+
215+
// on modern kernels sendfile can copy from any mmapable type (some but not all regular files and block devices)
216+
// to any writable file descriptor. On older kernels the writer side can only be a socket.
217+
// So we just try and fallback if needed.
218+
// If current file offsets + write sizes overflow it may also fail, we do not try to fix that and instead
219+
// fall back to the generic copy loop.
220+
if input_meta.potential_sendfile_source() {
221+
let result = sendfile_splice(SpliceMode::Sendfile, readfd, writefd, max_write);
222+
223+
match result {
224+
CopyResult::Ended(Ok(bytes_copied)) => return Ok(bytes_copied + written),
225+
CopyResult::Ended(err) => return err,
226+
CopyResult::Fallback(bytes) => written += bytes,
227+
}
194228
}
195-
(CopyParams(reader_meta, Some(readfd)), CopyParams(writer_meta, Some(writefd)))
196-
if reader_meta.is_fifo() || writer_meta.is_fifo() =>
197-
{
198-
// splice
199-
let bytes_flushed = flush()?;
200-
let max_write = reader.min_limit();
201-
let (mut reader, mut writer) =
202-
unsafe { (fd_as_file(readfd), fd_as_file(writefd)) };
203-
crate::sys::fs::sendfile_splice(
204-
crate::sys::fs::SpliceMode::Splice,
205-
&mut reader,
206-
&mut writer,
207-
max_write,
208-
)
209-
.map(|bytes_sent| bytes_sent + bytes_flushed)
229+
230+
if input_meta.maybe_fifo() || output_meta.maybe_fifo() {
231+
let result = sendfile_splice(SpliceMode::Splice, readfd, writefd, max_write);
232+
233+
match result {
234+
CopyResult::Ended(Ok(bytes_copied)) => return Ok(bytes_copied + written),
235+
CopyResult::Ended(err) => return err,
236+
CopyResult::Fallback(0) => { /* use fallback */ }
237+
CopyResult::Fallback(_) => {
238+
unreachable!("splice should not return > 0 bytes on the fallback path")
239+
}
240+
}
210241
}
211-
_ => super::generic_copy(reader, writer),
242+
}
243+
244+
match super::generic_copy(reader, writer) {
245+
Ok(bytes) => Ok(bytes + written),
246+
err => err,
212247
}
213248
}
214249
}
@@ -235,7 +270,10 @@ mod kernel_copy {
235270
fn properties(&self) -> CopyParams;
236271
}
237272

238-
impl<T> CopyRead for &mut T where T: CopyRead {
273+
impl<T> CopyRead for &mut T
274+
where
275+
T: CopyRead,
276+
{
239277
fn drain_to<W: Write>(&mut self, writer: &mut W, limit: u64) -> Result<u64> {
240278
(**self).drain_to(writer, limit)
241279
}
@@ -249,13 +287,15 @@ mod kernel_copy {
249287
}
250288
}
251289

252-
impl<T> CopyWrite for &mut T where T: CopyWrite {
290+
impl<T> CopyWrite for &mut T
291+
where
292+
T: CopyWrite,
293+
{
253294
fn properties(&self) -> CopyParams {
254295
(**self).properties()
255296
}
256297
}
257298

258-
259299
impl CopyRead for File {
260300
fn properties(&self) -> CopyParams {
261301
CopyParams(fd_to_meta(self), Some(self.as_raw_fd()))
@@ -270,13 +310,13 @@ mod kernel_copy {
270310

271311
impl CopyWrite for File {
272312
fn properties(&self) -> CopyParams {
273-
CopyParams(fd_to_meta(self), Some(self.as_raw_fd()))
313+
CopyParams(FdMeta::NoneObtained, Some(self.as_raw_fd()))
274314
}
275315
}
276316

277317
impl CopyWrite for &File {
278318
fn properties(&self) -> CopyParams {
279-
CopyParams(fd_to_meta(*self), Some(self.as_raw_fd()))
319+
CopyParams(FdMeta::NoneObtained, Some(self.as_raw_fd()))
280320
}
281321
}
282322

@@ -345,13 +385,13 @@ mod kernel_copy {
345385

346386
impl CopyWrite for StdoutLock<'_> {
347387
fn properties(&self) -> CopyParams {
348-
CopyParams(fd_to_meta(self), Some(self.as_raw_fd()))
388+
CopyParams(FdMeta::NoneObtained, Some(self.as_raw_fd()))
349389
}
350390
}
351391

352392
impl CopyWrite for StderrLock<'_> {
353393
fn properties(&self) -> CopyParams {
354-
CopyParams(fd_to_meta(self), Some(self.as_raw_fd()))
394+
CopyParams(FdMeta::NoneObtained, Some(self.as_raw_fd()))
355395
}
356396
}
357397

@@ -411,11 +451,7 @@ mod kernel_copy {
411451
let file: ManuallyDrop<File> = ManuallyDrop::new(unsafe { File::from_raw_fd(fd) });
412452
match file.metadata() {
413453
Ok(meta) => FdMeta::Metadata(meta),
414-
Err(_) => FdMeta::None,
454+
Err(_) => FdMeta::NoneObtained,
415455
}
416456
}
417-
418-
unsafe fn fd_as_file(fd: RawFd) -> ManuallyDrop<File> {
419-
ManuallyDrop::new(File::from_raw_fd(fd))
420-
}
421457
}

0 commit comments

Comments
 (0)