Skip to content

Commit bbfa92c

Browse files
committed
Always handle EOVERFLOW by falling back to the generic copy loop
Previously EOVERFLOW handling was only applied for io::copy specialization but not for fs::copy sharing the same code. Additionally we lower the chunk size to 1GB since we have a user report that older kernels may return EINVAL when passing 0x8000_0000 but smaller values succeed.
1 parent 4854d41 commit bbfa92c

File tree

2 files changed

+9
-11
lines changed

2 files changed

+9
-11
lines changed

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

+4-6
Original file line numberDiff line numberDiff line change
@@ -1199,12 +1199,10 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
11991199

12001200
match copy_regular_files(reader.as_raw_fd(), writer.as_raw_fd(), max_len) {
12011201
CopyResult::Ended(result) => result,
1202-
CopyResult::Fallback(written) => {
1203-
// fallback is only > 0 on EOVERFLOW, which shouldn't happen
1204-
// because the copy loop starts at a file offset 0 and countns down from `len`
1205-
assert_eq!(0, written);
1206-
io::copy::generic_copy(&mut reader, &mut writer)
1207-
}
1202+
CopyResult::Fallback(written) => match io::copy::generic_copy(&mut reader, &mut writer) {
1203+
Ok(bytes) => Ok(bytes + written),
1204+
Err(e) => Err(e),
1205+
},
12081206
}
12091207
}
12101208

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

+5-5
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,6 @@ pub(super) enum CopyResult {
438438
/// Callers must handle fallback to a generic copy loop.
439439
/// `Fallback` may indicate non-zero number of bytes already written
440440
/// if one of the files' cursor +`max_len` would exceed u64::MAX (`EOVERFLOW`).
441-
/// If the initial file offset was 0 then `Fallback` will only contain `0`.
442441
pub(super) fn copy_regular_files(reader: RawFd, writer: RawFd, max_len: u64) -> CopyResult {
443442
use crate::cmp;
444443

@@ -462,10 +461,10 @@ pub(super) fn copy_regular_files(reader: RawFd, writer: RawFd, max_len: u64) ->
462461
while written < max_len {
463462
let copy_result = if has_copy_file_range {
464463
let bytes_to_copy = cmp::min(max_len - written, usize::MAX as u64);
465-
// cap to 2GB chunks in case u64::MAX is passed in as file size and the file has a non-zero offset
466-
// this allows us to copy large chunks without hitting the limit,
467-
// unless someone sets a file offset close to u64::MAX - 2GB, in which case the fallback would kick in
468-
let bytes_to_copy = cmp::min(bytes_to_copy as usize, 0x8000_0000usize);
464+
// cap to 1GB chunks in case u64::MAX is passed as max_len and the file has a non-zero seek position
465+
// this allows us to copy large chunks without hitting EOVERFLOW,
466+
// unless someone sets a file offset close to u64::MAX - 1GB, in which case a fallback would be required
467+
let bytes_to_copy = cmp::min(bytes_to_copy as usize, 0x4000_0000usize);
469468
let copy_result = unsafe {
470469
// We actually don't have to adjust the offsets,
471470
// because copy_file_range adjusts the file offset automatically
@@ -560,6 +559,7 @@ fn sendfile_splice(mode: SpliceMode, reader: RawFd, writer: RawFd, len: u64) ->
560559

561560
let mut written = 0u64;
562561
while written < len {
562+
// according to its manpage that's the maximum size sendfile() will copy per invocation
563563
let chunk_size = crate::cmp::min(len - written, 0x7ffff000_u64) as usize;
564564

565565
let result = match mode {

0 commit comments

Comments
 (0)