Auto merge of #44895 - stephaneyfx:master, r=dtolnay

Made `fs::copy` return the length of the main stream

On Windows with the NTFS filesystem, `fs::copy` would return the sum of the
lengths of all streams, which can be different from the length reported by
`metadata` and thus confusing for users unaware of this NTFS peculiarity.

This makes `fs::copy` return the same length `metadata` reports which is the
value it used to return before PR #26751. Note that alternate streams are still
copied; their length is just not included in the returned value.

This change relies on the assumption that the stream with index 1 is always the
main stream in the `CopyFileEx` callback. I could not find any official
document confirming this but empirical testing has shown this to be true,
regardless of whether the alternate stream is created before or after the main
stream.

Resolves #44532
This commit is contained in:
bors 2017-10-03 22:19:40 +00:00
commit bd90aa6daa
2 changed files with 22 additions and 7 deletions

View File

@ -1374,14 +1374,17 @@ pub fn rename<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> io::Result<()>
/// Note that if `from` and `to` both point to the same file, then the file
/// will likely get truncated by this operation.
///
/// On success, the total number of bytes copied is returned.
/// On success, the total number of bytes copied is returned and it is equal to
/// the length of the `to` file as reported by `metadata`.
///
/// # Platform-specific behavior
///
/// 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 Windows, this function currently corresponds to `CopyFileEx`.
/// 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
@ -2589,13 +2592,25 @@ mod tests {
fn copy_file_preserves_streams() {
let tmp = tmpdir();
check!(check!(File::create(tmp.join("in.txt:bunny"))).write("carrot".as_bytes()));
assert_eq!(check!(fs::copy(tmp.join("in.txt"), tmp.join("out.txt"))), 6);
assert_eq!(check!(fs::copy(tmp.join("in.txt"), tmp.join("out.txt"))), 0);
assert_eq!(check!(tmp.join("out.txt").metadata()).len(), 0);
let mut v = Vec::new();
check!(check!(File::open(tmp.join("out.txt:bunny"))).read_to_end(&mut v));
assert_eq!(v, b"carrot".to_vec());
}
#[test]
fn copy_file_returns_metadata_len() {
let tmp = tmpdir();
let in_path = tmp.join("in.txt");
let out_path = tmp.join("out.txt");
check!(check!(File::create(&in_path)).write(b"lettuce"));
#[cfg(windows)]
check!(check!(File::create(tmp.join("in.txt:bunny"))).write(b"carrot"));
let copied_len = check!(fs::copy(&in_path, &out_path));
assert_eq!(check!(out_path.metadata()).len(), copied_len);
}
#[test]
fn symlinks_work() {
let tmpdir = tmpdir();

View File

@ -722,16 +722,16 @@ pub fn canonicalize(p: &Path) -> io::Result<PathBuf> {
pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
unsafe extern "system" fn callback(
_TotalFileSize: c::LARGE_INTEGER,
TotalBytesTransferred: c::LARGE_INTEGER,
_TotalBytesTransferred: c::LARGE_INTEGER,
_StreamSize: c::LARGE_INTEGER,
_StreamBytesTransferred: c::LARGE_INTEGER,
_dwStreamNumber: c::DWORD,
StreamBytesTransferred: c::LARGE_INTEGER,
dwStreamNumber: c::DWORD,
_dwCallbackReason: c::DWORD,
_hSourceFile: c::HANDLE,
_hDestinationFile: c::HANDLE,
lpData: c::LPVOID,
) -> c::DWORD {
*(lpData as *mut i64) = TotalBytesTransferred;
if dwStreamNumber == 1 {*(lpData as *mut i64) = StreamBytesTransferred;}
c::PROGRESS_CONTINUE
}
let pfrom = to_u16s(from)?;