-
Notifications
You must be signed in to change notification settings - Fork 12.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fs: copy: use copy_file_range on Linux #50772
Merged
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
834ef9f
fs: use copy_file_range on linux
nicokoch b4b71d5
Merge branch 'master' of https://github.com/nicokoch/rust
nicokoch 00ec3cf
Use copy_file_range on android also
nicokoch b605923
Add clarifying comment about offset argument
nicokoch f4c2825
Adjust len in every iteration
nicokoch a5e2942
Fix large file copies on 32 bit platforms
nicokoch 09d03bc
Store ENOSYS in a global to avoid unnecessary system calls
nicokoch 3f392ab
Implement suggestions from the PR
nicokoch 3b271eb
Use FIXME instead of TODO; Move bytes_to_copy calculation inside if
nicokoch c7d6a01
Fix additional nits:
nicokoch File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -761,6 +761,7 @@ pub fn canonicalize(p: &Path) -> io::Result<PathBuf> { | |
Ok(PathBuf::from(OsString::from_vec(buf))) | ||
} | ||
|
||
#[cfg(not(any(target_os = "linux", target_os = "android")))] | ||
pub fn copy(from: &Path, to: &Path) -> io::Result<u64> { | ||
use fs::{File, set_permissions}; | ||
if !from.is_file() { | ||
|
@@ -776,3 +777,90 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> { | |
set_permissions(to, perm)?; | ||
Ok(ret) | ||
} | ||
|
||
#[cfg(any(target_os = "linux", target_os = "android"))] | ||
pub fn copy(from: &Path, to: &Path) -> io::Result<u64> { | ||
use cmp; | ||
use fs::{File, set_permissions}; | ||
use sync::atomic::{AtomicBool, Ordering}; | ||
|
||
// Kernel prior to 4.5 don't have copy_file_range | ||
// We store the availability in a global to avoid unneccessary syscalls | ||
static HAS_COPY_FILE_RANGE: AtomicBool = AtomicBool::new(true); | ||
|
||
unsafe fn copy_file_range( | ||
fd_in: libc::c_int, | ||
off_in: *mut libc::loff_t, | ||
fd_out: libc::c_int, | ||
off_out: *mut libc::loff_t, | ||
len: libc::size_t, | ||
flags: libc::c_uint, | ||
) -> libc::c_long { | ||
libc::syscall( | ||
libc::SYS_copy_file_range, | ||
fd_in, | ||
off_in, | ||
fd_out, | ||
off_out, | ||
len, | ||
flags, | ||
) | ||
} | ||
|
||
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()) | ||
}; | ||
|
||
let has_copy_file_range = HAS_COPY_FILE_RANGE.load(Ordering::Relaxed); | ||
let mut written = 0u64; | ||
while written < len { | ||
let copy_result = if has_copy_file_range { | ||
let bytes_to_copy = cmp::min(len - written, usize::max_value() as u64) as usize; | ||
let copy_result = unsafe { | ||
// We actually don't have to adjust the offsets, | ||
// because copy_file_range adjusts the file offset automatically | ||
cvt(copy_file_range(reader.as_raw_fd(), | ||
ptr::null_mut(), | ||
writer.as_raw_fd(), | ||
ptr::null_mut(), | ||
bytes_to_copy, | ||
0) | ||
) | ||
}; | ||
if let Err(ref copy_err) = copy_result { | ||
if let Some(libc::ENOSYS) = copy_err.raw_os_error() { | ||
HAS_COPY_FILE_RANGE.store(false, Ordering::Relaxed); | ||
} | ||
} | ||
copy_result | ||
} else { | ||
Err(io::Error::from_raw_os_error(libc::ENOSYS)) | ||
}; | ||
match copy_result { | ||
Ok(ret) => written += ret as u64, | ||
Err(err) => { | ||
match err.raw_os_error() { | ||
Some(os_err) if os_err == libc::ENOSYS || os_err == libc::EXDEV => { | ||
// Either kernel is too old or the files are not mounted on the same fs. | ||
// Try again with fallback method | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you also throw an assert in here that |
||
assert_eq!(written, 0); | ||
let ret = io::copy(&mut reader, &mut writer)?; | ||
set_permissions(to, perm)?; | ||
return Ok(ret) | ||
}, | ||
_ => return Err(err), | ||
} | ||
} | ||
} | ||
} | ||
set_permissions(to, perm)?; | ||
Ok(written) | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't
has_copy_file_range
be updated to false as well ?For the first file, after the first chunk of the file has been copied, if copy_file_range was not available, next iterations of the while loop will try again to use copy_file_range and fail continuously, we could avoid it, making
mut has_copy_file_range
and herehas_copy_file_range = false;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No. If copy_result is Err the loop will always terminate in the current iteration.