diff --git a/library/std/src/sys/windows/c.rs b/library/std/src/sys/windows/c.rs index 8fe3b2638ec25..0906edb0d85e3 100644 --- a/library/std/src/sys/windows/c.rs +++ b/library/std/src/sys/windows/c.rs @@ -217,6 +217,7 @@ pub struct SYSTEMTIME { pub type LPSYSTEMTIME = *mut SYSTEMTIME; pub const INVALID_SET_FILE_POINTER: WIN32_ERROR = 0xFFFFFFFFu32; +pub const INVALID_FILE_SIZE: DWORD = 0xFFFFFFFF; pub unsafe extern "system" fn WriteFileEx( hFile: BorrowedHandle<'_>, @@ -536,6 +537,20 @@ compat_fn_with_fallback! { // just leak it on NT 3.1 TRUE } + + // >= NT 4+, 95+ (with unicows) + // https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-copyfileexw + pub fn CopyFileExW( + lpExistingFileName: PCWSTR, + lpNewFileName: PCWSTR, + lpProgressRoutine: LPPROGRESS_ROUTINE, + lpData: *const ::core::ffi::c_void, + pbCancel: *mut i32, + dwCopyFlags: u32 + ) -> BOOL { + SetLastError(ERROR_CALL_NOT_IMPLEMENTED as DWORD); + FALSE + } } compat_fn_optional! { @@ -722,4 +737,10 @@ extern "system" { pub fn GetTickCount() -> DWORD; pub fn GetCurrentThreadId() -> DWORD; pub fn GetVersion() -> DWORD; + pub fn GetFileSize(hFile: HANDLE, lpFileSizeHigh: *mut DWORD) -> DWORD; + pub fn CopyFileW( + lpExistingFileName: LPCWSTR, + lpNewFileName: LPCWSTR, + bFailIfExists: BOOL, + ) -> BOOL; } diff --git a/library/std/src/sys/windows/c/windows_sys.lst b/library/std/src/sys/windows/c/windows_sys.lst index 3dfb970191ae6..7e83e7143fcc6 100644 --- a/library/std/src/sys/windows/c/windows_sys.lst +++ b/library/std/src/sys/windows/c/windows_sys.lst @@ -2202,7 +2202,6 @@ Windows.Win32.Security.TOKEN_WRITE_OWNER Windows.Win32.Storage.FileSystem.BY_HANDLE_FILE_INFORMATION Windows.Win32.Storage.FileSystem.CALLBACK_CHUNK_FINISHED Windows.Win32.Storage.FileSystem.CALLBACK_STREAM_SWITCH -Windows.Win32.Storage.FileSystem.CopyFileExW Windows.Win32.Storage.FileSystem.CREATE_ALWAYS Windows.Win32.Storage.FileSystem.CREATE_NEW Windows.Win32.Storage.FileSystem.CreateDirectoryW diff --git a/library/std/src/sys/windows/c/windows_sys.rs b/library/std/src/sys/windows/c/windows_sys.rs index 754ad25fe8343..7b07fe3467cdb 100644 --- a/library/std/src/sys/windows/c/windows_sys.rs +++ b/library/std/src/sys/windows/c/windows_sys.rs @@ -26,17 +26,6 @@ extern "system" { ) -> COMPARESTRING_RESULT; } #[link(name = "kernel32")] -extern "system" { - pub fn CopyFileExW( - lpexistingfilename: PCWSTR, - lpnewfilename: PCWSTR, - lpprogressroutine: LPPROGRESS_ROUTINE, - lpdata: *const ::core::ffi::c_void, - pbcancel: *mut i32, - dwcopyflags: u32, - ) -> BOOL; -} -#[link(name = "kernel32")] extern "system" { pub fn CreateDirectoryW( lppathname: PCWSTR, diff --git a/library/std/src/sys/windows/fs.rs b/library/std/src/sys/windows/fs.rs index 2c1540c47e726..ec2a2cde81706 100644 --- a/library/std/src/sys/windows/fs.rs +++ b/library/std/src/sys/windows/fs.rs @@ -12,7 +12,7 @@ use crate::slice; use crate::sync::Arc; use crate::sys::handle::Handle; use crate::sys::time::SystemTime; -use crate::sys::{c, cvt, Align8}; +use crate::sys::{c, compat, cvt, Align8}; use crate::sys_common::{AsInner, FromInner, IntoInner}; use crate::thread; @@ -1419,36 +1419,88 @@ pub fn canonicalize(p: &Path) -> io::Result { } pub fn copy(from: &Path, to: &Path) -> io::Result { - unsafe extern "system" fn callback( - _TotalFileSize: c::LARGE_INTEGER, - _TotalBytesTransferred: c::LARGE_INTEGER, - _StreamSize: c::LARGE_INTEGER, - StreamBytesTransferred: c::LARGE_INTEGER, - dwStreamNumber: c::DWORD, - _dwCallbackReason: c::DWORD, - _hSourceFile: c::HANDLE, - _hDestinationFile: c::HANDLE, - lpData: c::LPCVOID, - ) -> c::DWORD { - if dwStreamNumber == 1 { - *(lpData as *mut i64) = StreamBytesTransferred; - } - c::PROGRESS_CONTINUE - } let pfrom = maybe_verbatim(from)?; let pto = maybe_verbatim(to)?; - let mut size = 0i64; - cvt(unsafe { - c::CopyFileExW( - pfrom.as_ptr(), - pto.as_ptr(), - Some(callback), - &mut size as *mut _ as *mut _, - ptr::null_mut(), - 0, - ) - })?; - Ok(size as u64) + + // NT 4+ + // + // For some reason, unicows implements CopyFileExW similarly to other functions (convert to + // ANSI, call ...A API). However, 9x/ME don't support CopyFileExA either! This means we have to + // check both for the API to exist *and* that we're running on NT. + if c::CopyFileExW::available() && compat::version::is_windows_nt() { + unsafe extern "system" fn callback( + _TotalFileSize: c::LARGE_INTEGER, + _TotalBytesTransferred: c::LARGE_INTEGER, + _StreamSize: c::LARGE_INTEGER, + StreamBytesTransferred: c::LARGE_INTEGER, + dwStreamNumber: c::DWORD, + _dwCallbackReason: c::DWORD, + _hSourceFile: c::HANDLE, + _hDestinationFile: c::HANDLE, + lpData: c::LPCVOID, + ) -> c::DWORD { + if dwStreamNumber == 1 { + *(lpData as *mut i64) = StreamBytesTransferred; + } + c::PROGRESS_CONTINUE + } + + let mut size = 0i64; + cvt(unsafe { + c::CopyFileExW( + pfrom.as_ptr(), + pto.as_ptr(), + Some(callback), + &mut size as *mut _ as *mut _, + ptr::null_mut(), + 0, + ) + })?; + Ok(size as u64) + } else { + // NT 3.51 and earlier, or 9x/ME + + // If `CopyFileExW` is not available, we have to copy the file with the non-Ex API, + // then open it with `dwDesiredAccess = 0` (query attributes only), + // then use `GetFileSize` to retrieve the size + cvt(unsafe { + c::CopyFileW( + pfrom.as_ptr(), + pto.as_ptr(), + c::FALSE, // FALSE: allow overwriting + ) + })?; + + let handle = unsafe { + c::CreateFileW( + pto.as_ptr(), + 0, + c::FILE_SHARE_READ | c::FILE_SHARE_WRITE, + ptr::null_mut(), + c::OPEN_EXISTING, + 0, + ptr::null_mut(), + ) + }; + + let handle = if let Ok(handle) = OwnedHandle::try_from(handle) { + handle + } else { + return Err(Error::last_os_error()); + }; + + let mut upper_dword: c::DWORD = 0; + let lower_dword = unsafe { c::GetFileSize(handle.as_raw_handle(), &mut upper_dword) }; + + if lower_dword == c::INVALID_FILE_SIZE { + let errno = crate::sys::os::errno(); + if errno != c::STATUS_SUCCESS { + return Err(Error::from_raw_os_error(errno)); + } + } + + Ok((upper_dword as u64) << 32 | lower_dword as u64) + } } #[allow(dead_code)]