Skip to content

Commit 7f5d272

Browse files
committed
move copy specialization into sys::unix module
1 parent ad9b07c commit 7f5d272

File tree

4 files changed

+435
-392
lines changed

4 files changed

+435
-392
lines changed

library/std/src/io/copy.rs

Lines changed: 8 additions & 377 deletions
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,17 @@ where
4545
R: Read,
4646
W: Write,
4747
{
48-
#[cfg(any(target_os = "linux", target_os = "android"))]
49-
{
50-
kernel_copy::copy_spec(reader, writer)
48+
cfg_if::cfg_if! {
49+
if #[cfg(any(target_os = "linux", target_os = "android"))] {
50+
crate::sys::kernel_copy::copy_spec(reader, writer)
51+
} else {
52+
generic_copy(reader, writer)
53+
}
5154
}
52-
53-
#[cfg(not(any(target_os = "linux", target_os = "android")))]
54-
generic_copy(reader, writer)
5555
}
5656

57+
/// The general read-write-loop implementation of
58+
/// `io::copy` that is used when specializations are not available or not applicable.
5759
pub(crate) fn generic_copy<R: ?Sized, W: ?Sized>(reader: &mut R, writer: &mut W) -> io::Result<u64>
5860
where
5961
R: Read,
@@ -84,374 +86,3 @@ where
8486
written += len as u64;
8587
}
8688
}
87-
88-
#[cfg(any(target_os = "linux", target_os = "android"))]
89-
mod kernel_copy {
90-
91-
use crate::cmp::min;
92-
use crate::convert::TryInto;
93-
use crate::fs::{File, Metadata};
94-
use crate::io::{
95-
BufRead, BufReader, BufWriter, Read, Result, StderrLock, StdinLock, StdoutLock, Take, Write,
96-
};
97-
use crate::mem::ManuallyDrop;
98-
use crate::net::TcpStream;
99-
use crate::os::unix::fs::FileTypeExt;
100-
use crate::os::unix::io::{AsRawFd, FromRawFd, RawFd};
101-
use crate::process::{ChildStderr, ChildStdin, ChildStdout};
102-
use crate::sys::fs::{copy_regular_files, sendfile_splice, CopyResult, SpliceMode};
103-
104-
pub(super) fn copy_spec<R: Read + ?Sized, W: Write + ?Sized>(
105-
read: &mut R,
106-
write: &mut W,
107-
) -> Result<u64> {
108-
let copier = Copier { read, write };
109-
SpecCopy::copy(copier)
110-
}
111-
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.
117-
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)
120-
Metadata(Metadata),
121-
Socket,
122-
Pipe,
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,
127-
}
128-
129-
impl FdMeta {
130-
fn maybe_fifo(&self) -> bool {
131-
match self {
132-
FdMeta::Metadata(meta) => meta.file_type().is_fifo(),
133-
FdMeta::Socket => false,
134-
FdMeta::Pipe => true,
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,
161-
}
162-
}
163-
}
164-
165-
struct CopyParams(FdMeta, Option<RawFd>);
166-
167-
struct Copier<'a, 'b, R: Read + ?Sized, W: Write + ?Sized> {
168-
pub read: &'a mut R,
169-
pub write: &'b mut W,
170-
}
171-
172-
trait SpecCopy {
173-
fn copy(self) -> Result<u64>;
174-
}
175-
176-
impl<R: Read + ?Sized, W: Write + ?Sized> SpecCopy for Copier<'_, '_, R, W> {
177-
default fn copy(self) -> Result<u64> {
178-
super::generic_copy(self.read, self.write)
179-
}
180-
}
181-
182-
impl<R: CopyRead, W: CopyWrite> SpecCopy for Copier<'_, '_, R, W> {
183-
fn copy(self) -> Result<u64> {
184-
let (reader, writer) = (self.read, self.write);
185-
let r_cfg = reader.properties();
186-
let w_cfg = writer.properties();
187-
188-
// before direct operations on file descriptors ensure that all source and sink buffers are emtpy
189-
let mut flush = || -> crate::io::Result<u64> {
190-
let bytes = reader.drain_to(writer, u64::MAX)?;
191-
// BufWriter buffered bytes have already been accounted for in earlier write() calls
192-
writer.flush()?;
193-
Ok(bytes)
194-
};
195-
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-
}
213-
}
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-
}
228-
}
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-
}
241-
}
242-
}
243-
244-
match super::generic_copy(reader, writer) {
245-
Ok(bytes) => Ok(bytes + written),
246-
err => err,
247-
}
248-
}
249-
}
250-
251-
#[rustc_specialization_trait]
252-
trait CopyRead: Read {
253-
fn drain_to<W: Write>(&mut self, _writer: &mut W, _limit: u64) -> Result<u64> {
254-
Ok(0)
255-
}
256-
257-
/// The minimum of the limit of all `Take<_>` wrappers, `u64::MAX` otherwise.
258-
/// This method does not account for data `BufReader` buffers and would underreport
259-
/// the limit of a `Take<BufReader<Take<_>>>` type. Thus its result is only valid
260-
/// after draining the buffers.
261-
fn min_limit(&self) -> u64 {
262-
u64::MAX
263-
}
264-
265-
fn properties(&self) -> CopyParams;
266-
}
267-
268-
#[rustc_specialization_trait]
269-
trait CopyWrite: Write {
270-
fn properties(&self) -> CopyParams;
271-
}
272-
273-
impl<T> CopyRead for &mut T
274-
where
275-
T: CopyRead,
276-
{
277-
fn drain_to<W: Write>(&mut self, writer: &mut W, limit: u64) -> Result<u64> {
278-
(**self).drain_to(writer, limit)
279-
}
280-
281-
fn min_limit(&self) -> u64 {
282-
(**self).min_limit()
283-
}
284-
285-
fn properties(&self) -> CopyParams {
286-
(**self).properties()
287-
}
288-
}
289-
290-
impl<T> CopyWrite for &mut T
291-
where
292-
T: CopyWrite,
293-
{
294-
fn properties(&self) -> CopyParams {
295-
(**self).properties()
296-
}
297-
}
298-
299-
impl CopyRead for File {
300-
fn properties(&self) -> CopyParams {
301-
CopyParams(fd_to_meta(self), Some(self.as_raw_fd()))
302-
}
303-
}
304-
305-
impl CopyRead for &File {
306-
fn properties(&self) -> CopyParams {
307-
CopyParams(fd_to_meta(*self), Some(self.as_raw_fd()))
308-
}
309-
}
310-
311-
impl CopyWrite for File {
312-
fn properties(&self) -> CopyParams {
313-
CopyParams(FdMeta::NoneObtained, Some(self.as_raw_fd()))
314-
}
315-
}
316-
317-
impl CopyWrite for &File {
318-
fn properties(&self) -> CopyParams {
319-
CopyParams(FdMeta::NoneObtained, Some(self.as_raw_fd()))
320-
}
321-
}
322-
323-
impl CopyRead for TcpStream {
324-
fn properties(&self) -> CopyParams {
325-
// avoid the stat syscall since we can be fairly sure it's a socket
326-
CopyParams(FdMeta::Socket, Some(self.as_raw_fd()))
327-
}
328-
}
329-
330-
impl CopyRead for &TcpStream {
331-
fn properties(&self) -> CopyParams {
332-
// avoid the stat syscall since we can be fairly sure it's a socket
333-
CopyParams(FdMeta::Socket, Some(self.as_raw_fd()))
334-
}
335-
}
336-
337-
impl CopyWrite for TcpStream {
338-
fn properties(&self) -> CopyParams {
339-
// avoid the stat syscall since we can be fairly sure it's a socket
340-
CopyParams(FdMeta::Socket, Some(self.as_raw_fd()))
341-
}
342-
}
343-
344-
impl CopyWrite for &TcpStream {
345-
fn properties(&self) -> CopyParams {
346-
// avoid the stat syscall since we can be fairly sure it's a socket
347-
CopyParams(FdMeta::Socket, Some(self.as_raw_fd()))
348-
}
349-
}
350-
351-
impl CopyWrite for ChildStdin {
352-
fn properties(&self) -> CopyParams {
353-
CopyParams(FdMeta::Pipe, Some(self.as_raw_fd()))
354-
}
355-
}
356-
357-
impl CopyRead for ChildStdout {
358-
fn properties(&self) -> CopyParams {
359-
CopyParams(FdMeta::Pipe, Some(self.as_raw_fd()))
360-
}
361-
}
362-
363-
impl CopyRead for ChildStderr {
364-
fn properties(&self) -> CopyParams {
365-
CopyParams(FdMeta::Pipe, Some(self.as_raw_fd()))
366-
}
367-
}
368-
369-
impl CopyRead for StdinLock<'_> {
370-
fn drain_to<W: Write>(&mut self, writer: &mut W, outer_limit: u64) -> Result<u64> {
371-
let buf_reader = self.as_mut_buf();
372-
let buf = buf_reader.buffer();
373-
let buf = &buf[0..min(buf.len(), outer_limit.try_into().unwrap_or(usize::MAX))];
374-
let bytes_drained = buf.len();
375-
writer.write_all(buf)?;
376-
buf_reader.consume(bytes_drained);
377-
378-
Ok(bytes_drained as u64)
379-
}
380-
381-
fn properties(&self) -> CopyParams {
382-
CopyParams(fd_to_meta(self), Some(self.as_raw_fd()))
383-
}
384-
}
385-
386-
impl CopyWrite for StdoutLock<'_> {
387-
fn properties(&self) -> CopyParams {
388-
CopyParams(FdMeta::NoneObtained, Some(self.as_raw_fd()))
389-
}
390-
}
391-
392-
impl CopyWrite for StderrLock<'_> {
393-
fn properties(&self) -> CopyParams {
394-
CopyParams(FdMeta::NoneObtained, Some(self.as_raw_fd()))
395-
}
396-
}
397-
398-
impl<T: CopyRead> CopyRead for Take<T> {
399-
fn drain_to<W: Write>(&mut self, writer: &mut W, outer_limit: u64) -> Result<u64> {
400-
let local_limit = self.limit();
401-
let combined_limit = min(outer_limit, local_limit);
402-
let bytes_drained = self.get_mut().drain_to(writer, combined_limit)?;
403-
// update limit since read() was bypassed
404-
self.set_limit(local_limit - bytes_drained);
405-
406-
Ok(bytes_drained)
407-
}
408-
409-
fn min_limit(&self) -> u64 {
410-
min(Take::limit(self), self.get_ref().min_limit())
411-
}
412-
413-
fn properties(&self) -> CopyParams {
414-
self.get_ref().properties()
415-
}
416-
}
417-
418-
impl<T: CopyRead> CopyRead for BufReader<T> {
419-
fn drain_to<W: Write>(&mut self, writer: &mut W, outer_limit: u64) -> Result<u64> {
420-
let buf = self.buffer();
421-
let buf = &buf[0..min(buf.len(), outer_limit.try_into().unwrap_or(usize::MAX))];
422-
let bytes = buf.len();
423-
writer.write_all(buf)?;
424-
self.consume(bytes);
425-
426-
let remaining = outer_limit - bytes as u64;
427-
428-
// in case of nested bufreaders we also need to drain the ones closer to the source
429-
let inner_bytes = self.get_mut().drain_to(writer, remaining)?;
430-
431-
Ok(bytes as u64 + inner_bytes)
432-
}
433-
434-
fn min_limit(&self) -> u64 {
435-
self.get_ref().min_limit()
436-
}
437-
438-
fn properties(&self) -> CopyParams {
439-
self.get_ref().properties()
440-
}
441-
}
442-
443-
impl<T: CopyWrite> CopyWrite for BufWriter<T> {
444-
fn properties(&self) -> CopyParams {
445-
self.get_ref().properties()
446-
}
447-
}
448-
449-
fn fd_to_meta<T: AsRawFd>(fd: &T) -> FdMeta {
450-
let fd = fd.as_raw_fd();
451-
let file: ManuallyDrop<File> = ManuallyDrop::new(unsafe { File::from_raw_fd(fd) });
452-
match file.metadata() {
453-
Ok(meta) => FdMeta::Metadata(meta),
454-
Err(_) => FdMeta::NoneObtained,
455-
}
456-
}
457-
}

0 commit comments

Comments
 (0)