Skip to content

Commit ea62d85

Browse files
seritoolsmbilker
authored andcommitted
Add fallback implementation for CopyFileExW
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 For some reason, the unicows layer 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.
1 parent fcbffcb commit ea62d85

File tree

4 files changed

+102
-41
lines changed

4 files changed

+102
-41
lines changed

library/std/src/sys/windows/c.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ pub struct SYSTEMTIME {
217217
pub type LPSYSTEMTIME = *mut SYSTEMTIME;
218218

219219
pub const INVALID_SET_FILE_POINTER: WIN32_ERROR = 0xFFFFFFFFu32;
220+
pub const INVALID_FILE_SIZE: DWORD = 0xFFFFFFFF;
220221

221222
pub unsafe extern "system" fn WriteFileEx(
222223
hFile: BorrowedHandle<'_>,
@@ -536,6 +537,20 @@ compat_fn_with_fallback! {
536537
// just leak it on NT 3.1
537538
TRUE
538539
}
540+
541+
// >= NT 4+, 95+ (with unicows)
542+
// https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-copyfileexw
543+
pub fn CopyFileExW(
544+
lpExistingFileName: PCWSTR,
545+
lpNewFileName: PCWSTR,
546+
lpProgressRoutine: LPPROGRESS_ROUTINE,
547+
lpData: *const ::core::ffi::c_void,
548+
pbCancel: *mut i32,
549+
dwCopyFlags: u32
550+
) -> BOOL {
551+
SetLastError(ERROR_CALL_NOT_IMPLEMENTED as DWORD);
552+
FALSE
553+
}
539554
}
540555

541556
compat_fn_optional! {
@@ -722,4 +737,10 @@ extern "system" {
722737
pub fn GetTickCount() -> DWORD;
723738
pub fn GetCurrentThreadId() -> DWORD;
724739
pub fn GetVersion() -> DWORD;
740+
pub fn GetFileSize(hFile: HANDLE, lpFileSizeHigh: *mut DWORD) -> DWORD;
741+
pub fn CopyFileW(
742+
lpExistingFileName: LPCWSTR,
743+
lpNewFileName: LPCWSTR,
744+
bFailIfExists: BOOL,
745+
) -> BOOL;
725746
}

library/std/src/sys/windows/c/windows_sys.lst

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2202,7 +2202,6 @@ Windows.Win32.Security.TOKEN_WRITE_OWNER
22022202
Windows.Win32.Storage.FileSystem.BY_HANDLE_FILE_INFORMATION
22032203
Windows.Win32.Storage.FileSystem.CALLBACK_CHUNK_FINISHED
22042204
Windows.Win32.Storage.FileSystem.CALLBACK_STREAM_SWITCH
2205-
Windows.Win32.Storage.FileSystem.CopyFileExW
22062205
Windows.Win32.Storage.FileSystem.CREATE_ALWAYS
22072206
Windows.Win32.Storage.FileSystem.CREATE_NEW
22082207
Windows.Win32.Storage.FileSystem.CreateDirectoryW

library/std/src/sys/windows/c/windows_sys.rs

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,6 @@ extern "system" {
2626
) -> COMPARESTRING_RESULT;
2727
}
2828
#[link(name = "kernel32")]
29-
extern "system" {
30-
pub fn CopyFileExW(
31-
lpexistingfilename: PCWSTR,
32-
lpnewfilename: PCWSTR,
33-
lpprogressroutine: LPPROGRESS_ROUTINE,
34-
lpdata: *const ::core::ffi::c_void,
35-
pbcancel: *mut i32,
36-
dwcopyflags: u32,
37-
) -> BOOL;
38-
}
39-
#[link(name = "kernel32")]
4029
extern "system" {
4130
pub fn CreateDirectoryW(
4231
lppathname: PCWSTR,

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

Lines changed: 81 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use crate::slice;
1212
use crate::sync::Arc;
1313
use crate::sys::handle::Handle;
1414
use crate::sys::time::SystemTime;
15-
use crate::sys::{c, cvt, Align8};
15+
use crate::sys::{c, compat, cvt, Align8};
1616
use crate::sys_common::{AsInner, FromInner, IntoInner};
1717
use crate::thread;
1818

@@ -1419,36 +1419,88 @@ pub fn canonicalize(p: &Path) -> io::Result<PathBuf> {
14191419
}
14201420

14211421
pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
1422-
unsafe extern "system" fn callback(
1423-
_TotalFileSize: c::LARGE_INTEGER,
1424-
_TotalBytesTransferred: c::LARGE_INTEGER,
1425-
_StreamSize: c::LARGE_INTEGER,
1426-
StreamBytesTransferred: c::LARGE_INTEGER,
1427-
dwStreamNumber: c::DWORD,
1428-
_dwCallbackReason: c::DWORD,
1429-
_hSourceFile: c::HANDLE,
1430-
_hDestinationFile: c::HANDLE,
1431-
lpData: c::LPCVOID,
1432-
) -> c::DWORD {
1433-
if dwStreamNumber == 1 {
1434-
*(lpData as *mut i64) = StreamBytesTransferred;
1435-
}
1436-
c::PROGRESS_CONTINUE
1437-
}
14381422
let pfrom = maybe_verbatim(from)?;
14391423
let pto = maybe_verbatim(to)?;
1440-
let mut size = 0i64;
1441-
cvt(unsafe {
1442-
c::CopyFileExW(
1443-
pfrom.as_ptr(),
1444-
pto.as_ptr(),
1445-
Some(callback),
1446-
&mut size as *mut _ as *mut _,
1447-
ptr::null_mut(),
1448-
0,
1449-
)
1450-
})?;
1451-
Ok(size as u64)
1424+
1425+
// NT 4+
1426+
//
1427+
// For some reason, unicows implements CopyFileExW similarly to other functions (convert to
1428+
// ANSI, call ...A API). However, 9x/ME don't support CopyFileExA either! This means we have to
1429+
// check both for the API to exist *and* that we're running on NT.
1430+
if c::CopyFileExW::available() && compat::version::is_windows_nt() {
1431+
unsafe extern "system" fn callback(
1432+
_TotalFileSize: c::LARGE_INTEGER,
1433+
_TotalBytesTransferred: c::LARGE_INTEGER,
1434+
_StreamSize: c::LARGE_INTEGER,
1435+
StreamBytesTransferred: c::LARGE_INTEGER,
1436+
dwStreamNumber: c::DWORD,
1437+
_dwCallbackReason: c::DWORD,
1438+
_hSourceFile: c::HANDLE,
1439+
_hDestinationFile: c::HANDLE,
1440+
lpData: c::LPCVOID,
1441+
) -> c::DWORD {
1442+
if dwStreamNumber == 1 {
1443+
*(lpData as *mut i64) = StreamBytesTransferred;
1444+
}
1445+
c::PROGRESS_CONTINUE
1446+
}
1447+
1448+
let mut size = 0i64;
1449+
cvt(unsafe {
1450+
c::CopyFileExW(
1451+
pfrom.as_ptr(),
1452+
pto.as_ptr(),
1453+
Some(callback),
1454+
&mut size as *mut _ as *mut _,
1455+
ptr::null_mut(),
1456+
0,
1457+
)
1458+
})?;
1459+
Ok(size as u64)
1460+
} else {
1461+
// NT 3.51 and earlier, or 9x/ME
1462+
1463+
// If `CopyFileExW` is not available, we have to copy the file with the non-Ex API,
1464+
// then open it with `dwDesiredAccess = 0` (query attributes only),
1465+
// then use `GetFileSize` to retrieve the size
1466+
cvt(unsafe {
1467+
c::CopyFileW(
1468+
pfrom.as_ptr(),
1469+
pto.as_ptr(),
1470+
c::FALSE, // FALSE: allow overwriting
1471+
)
1472+
})?;
1473+
1474+
let handle = unsafe {
1475+
c::CreateFileW(
1476+
pto.as_ptr(),
1477+
0,
1478+
c::FILE_SHARE_READ | c::FILE_SHARE_WRITE,
1479+
ptr::null_mut(),
1480+
c::OPEN_EXISTING,
1481+
0,
1482+
ptr::null_mut(),
1483+
)
1484+
};
1485+
1486+
let handle = if let Ok(handle) = OwnedHandle::try_from(handle) {
1487+
handle
1488+
} else {
1489+
return Err(Error::last_os_error());
1490+
};
1491+
1492+
let mut upper_dword: c::DWORD = 0;
1493+
let lower_dword = unsafe { c::GetFileSize(handle.as_raw_handle(), &mut upper_dword) };
1494+
1495+
if lower_dword == c::INVALID_FILE_SIZE {
1496+
let errno = crate::sys::os::errno();
1497+
if errno != c::STATUS_SUCCESS {
1498+
return Err(Error::from_raw_os_error(errno));
1499+
}
1500+
}
1501+
1502+
Ok((upper_dword as u64) << 32 | lower_dword as u64)
1503+
}
14521504
}
14531505

14541506
#[allow(dead_code)]

0 commit comments

Comments
 (0)