Skip to content
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

Sparse-file handling for the Linux implementation of std::fs::copy() #55909

Closed
wants to merge 27 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
52f86d2
Start of testing for fs.rs.
tarka Nov 3, 2018
2e7931b
Add failing test of sparse copy.
tarka Nov 3, 2018
822760a
Add some more test cases.
tarka Nov 3, 2018
00e8db0
Minor test cleanups.
tarka Nov 4, 2018
e838cbd
Add sparse detection routine.
tarka Nov 4, 2018
abc69fe
Add test of current simple file copy before refactoring.
tarka Nov 6, 2018
b5eef91
Disable (deliberatly) non-working tests for now.
tarka Nov 6, 2018
c0d936a
Refactor copy_file_range() call to enable copying of chunks.
tarka Nov 6, 2018
e6b19ec
Break Linux copy function out to own mod for sparse refactoring.
tarka Nov 6, 2018
d356451
Move some internal fns up to root.
tarka Nov 7, 2018
69cb124
Add functions to handle sparse files, with tests. Not used yet.
tarka Nov 7, 2018
091eec7
Add tests with sparse data.
tarka Nov 8, 2018
be75c6f
Interim checkin; start moving kernel/uspace branch to lower-level fn.
tarka Nov 8, 2018
dfc92e5
Move flag of copy_file_range to thread-local, and move the decision t…
tarka Nov 9, 2018
84313fb
Add override of kernel vs userspace copy for cross-device copy.
tarka Nov 9, 2018
16c4010
Minor cleanup of tests.
tarka Nov 10, 2018
054990f
Initial cut of user-space copy range.
tarka Nov 11, 2018
95460c8
Do cross-mount detection up-front, and replace current copy impl with…
tarka Nov 11, 2018
b6f6f97
Enable disabled tests.
tarka Nov 11, 2018
5b84746
Port in external test module as we now have a local one, and use std-…
tarka Nov 11, 2018
ce17903
Test and fix for userspace copies larger than read blocks.
tarka Nov 12, 2018
9e52d35
Use MetadataExt rather than direct stat() call.
tarka Nov 13, 2018
b933836
Minor test and fmt cleanups.
tarka Nov 13, 2018
92283c8
Travis runs under 3.x kernel, so we need to disable any tests that re…
tarka Nov 14, 2018
a437a7a
Add notes about platform-specific handling in copy().
tarka Nov 14, 2018
a435874
Minimise calls to .metadata().
tarka Nov 20, 2018
f1e7e35
Add copyright header.
tarka Nov 20, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions src/libstd/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1569,20 +1569,30 @@ pub fn rename<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> io::Result<()>
/// If you’re wanting to copy the contents of one file to another and you’re
/// working with [`File`]s, see the [`io::copy`] function.
///
/// [`io::copy`]: ../io/fn.copy.html
/// [`File`]: ./struct.File.html
///
/// # Platform-specific behavior
///
/// Note that, these behaviours [may change in the future][changes].
///
/// ## Unix-like
///
/// This function currently corresponds to the `open` function in Unix
/// with `O_RDONLY` for `from` and `O_WRONLY`, `O_CREAT`, and `O_TRUNC` for `to`.
/// `O_CLOEXEC` is set for returned file descriptors.
///
/// On Linux this call will attempt to preserve [sparse-files][sparse]
/// if the target filesystem supports them. If this is not the desired
/// behaviour use [`io::copy`].
///
/// ## Windows
///
/// On Windows, this function currently corresponds to `CopyFileEx`. Alternate
/// NTFS streams are copied but only the size of the main stream is returned by
/// this function.
/// Note that, this [may change in the future][changes].
///
/// [changes]: ../io/index.html#platform-specific-behavior
/// [sparse]: https://en.wikipedia.org/wiki/Sparse_file
/// [`io::copy`]: ../io/fn.copy.html
/// [`File`]: ./struct.File.html
///
/// # Errors
///
Expand Down
5 changes: 3 additions & 2 deletions src/libstd/io/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ use mem;
/// On success, the total number of bytes that were copied from
/// `reader` to `writer` is returned.
///
/// If you’re wanting to copy the contents of one file to another and you’re
/// working with filesystem paths, see the [`fs::copy`] function.
/// If you’re copying the contents of one file to another and you’re
/// working with filesystem paths, see the [`fs::copy`] function,
/// which handles permissions and platform-specific optimisations.
///
/// [`fs::copy`]: ../fs/fn.copy.html
///
Expand Down
96 changes: 3 additions & 93 deletions src/libstd/sys/unix/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use os::unix::prelude::*;

use ffi::{CString, CStr, OsString, OsStr};
use fmt;
use io::{self, Error, ErrorKind, SeekFrom};
use io::{self, Error, SeekFrom};
use libc::{self, c_int, mode_t};
use mem;
use path::{Path, PathBuf};
Expand Down Expand Up @@ -850,96 +850,6 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
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;
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 unnecessary 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 {
match copy_err.raw_os_error() {
Some(libc::ENOSYS) | Some(libc::EPERM) => {
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
|| os_err == libc::EPERM => {
// Try fallback io::copy if either:
// - Kernel version is < 4.5 (ENOSYS)
// - Files are mounted on different fs (EXDEV)
// - copy_file_range is disallowed, for example by seccomp (EPERM)
assert_eq!(written, 0);
let ret = io::copy(&mut reader, &mut writer)?;
writer.set_permissions(perm)?;
return Ok(ret)
},
_ => return Err(err),
}
}
}
}
writer.set_permissions(perm)?;
Ok(written)
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub use super::fs_linux::copy;
Loading