Skip to content

Commit 0bb64a8

Browse files
committed
Add fallback implementation for CopyFileExW and MoveFileExW
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 `MoveFileExW` falls back to calls to `CopyFileW` and `DeleteFile`.
1 parent abe2a0f commit 0bb64a8

File tree

4 files changed

+129
-31
lines changed

4 files changed

+129
-31
lines changed

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,17 @@ compat_fn_lazy! {
453453
cchfilepath: u32,
454454
dwflags: GETFINALPATHNAMEBYHANDLE_FLAGS,
455455
) -> u32;
456+
457+
// >= NT 4+; partial: 95+ (provided by unicows, returns "not implemented")
458+
// https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-copyfileexw
459+
pub fn CopyFileExW(
460+
lpexistingfilename: PCWSTR,
461+
lpnewfilename: PCWSTR,
462+
lpprogressroutine: LPPROGRESS_ROUTINE,
463+
lpdata: *const ::core::ffi::c_void,
464+
pbcancel: *mut BOOL,
465+
dwcopyflags: u32,
466+
) -> BOOL;
456467
}
457468

458469
compat_fn_optional! {

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2626,3 +2626,8 @@ Windows.Win32.Networking.WinSock.WSADuplicateSocketA
26262626

26272627
// NT vs 9x compat
26282628
Windows.Win32.System.SystemInformation.GetVersion
2629+
2630+
// file ops fallbacks
2631+
Windows.Win32.Storage.FileSystem.CopyFileW
2632+
Windows.Win32.Storage.FileSystem.GetFileSize
2633+
Windows.Win32.Storage.FileSystem.INVALID_FILE_SIZE

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,14 @@ extern "system" {
6868
) -> BOOL;
6969
}
7070
#[link(name = "kernel32")]
71+
extern "system" {
72+
pub fn CopyFileW(
73+
lpexistingfilename: PCWSTR,
74+
lpnewfilename: PCWSTR,
75+
bfailifexists: BOOL,
76+
) -> BOOL;
77+
}
78+
#[link(name = "kernel32")]
7179
extern "system" {
7280
pub fn CreateDirectoryW(
7381
lppathname: PCWSTR,
@@ -318,6 +326,10 @@ extern "system" {
318326
) -> BOOL;
319327
}
320328
#[link(name = "kernel32")]
329+
extern "system" {
330+
pub fn GetFileSize(hfile: HANDLE, lpfilesizehigh: *mut u32) -> u32;
331+
}
332+
#[link(name = "kernel32")]
321333
extern "system" {
322334
pub fn GetFileType(hfile: HANDLE) -> FILE_TYPE;
323335
}
@@ -3610,6 +3622,7 @@ impl ::core::clone::Clone for INIT_ONCE {
36103622
}
36113623
pub const INIT_ONCE_INIT_FAILED: u32 = 4u32;
36123624
pub const INVALID_FILE_ATTRIBUTES: u32 = 4294967295u32;
3625+
pub const INVALID_FILE_SIZE: u32 = 4294967295u32;
36133626
pub const INVALID_SET_FILE_POINTER: u32 = 4294967295u32;
36143627
pub const INVALID_SOCKET: SOCKET = -1i32 as _;
36153628
#[repr(C)]

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

Lines changed: 100 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use crate::sys_common::{AsInner, FromInner, IntoInner};
1717
use crate::thread;
1818

1919
use super::path::maybe_verbatim;
20-
use super::{api, to_u16s, IoResult};
20+
use super::{api, compat, to_u16s, IoResult};
2121

2222
pub struct File {
2323
handle: Handle,
@@ -1098,8 +1098,22 @@ pub fn unlink(p: &Path) -> io::Result<()> {
10981098
pub fn rename(old: &Path, new: &Path) -> io::Result<()> {
10991099
let old = maybe_verbatim(old)?;
11001100
let new = maybe_verbatim(new)?;
1101-
cvt(unsafe { c::MoveFileExW(old.as_ptr(), new.as_ptr(), c::MOVEFILE_REPLACE_EXISTING) })?;
1102-
Ok(())
1101+
let res =
1102+
cvt(unsafe { c::MoveFileExW(old.as_ptr(), new.as_ptr(), c::MOVEFILE_REPLACE_EXISTING) });
1103+
1104+
match res {
1105+
Err(e) if e.raw_os_error() == Some(c::ERROR_CALL_NOT_IMPLEMENTED as i32) => {
1106+
// 9x/ME doesn't support MoveFileEx, so we fall back to copy + delete and hope for the
1107+
// best
1108+
unsafe {
1109+
cvt(c::CopyFileW(old.as_ptr(), new.as_ptr(), c::TRUE))?;
1110+
cvt(c::DeleteFileW(old.as_ptr()))?;
1111+
Ok(())
1112+
}
1113+
}
1114+
Err(e) => Err(e),
1115+
Ok(_) => Ok(()),
1116+
}
11031117
}
11041118

11051119
pub fn rmdir(p: &Path) -> io::Result<()> {
@@ -1402,36 +1416,91 @@ pub fn canonicalize(p: &Path) -> io::Result<PathBuf> {
14021416
}
14031417

14041418
pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
1405-
unsafe extern "system" fn callback(
1406-
_TotalFileSize: c::LARGE_INTEGER,
1407-
_TotalBytesTransferred: c::LARGE_INTEGER,
1408-
_StreamSize: c::LARGE_INTEGER,
1409-
StreamBytesTransferred: c::LARGE_INTEGER,
1410-
dwStreamNumber: c::DWORD,
1411-
_dwCallbackReason: c::DWORD,
1412-
_hSourceFile: c::HANDLE,
1413-
_hDestinationFile: c::HANDLE,
1414-
lpData: c::LPCVOID,
1415-
) -> c::DWORD {
1416-
if dwStreamNumber == 1 {
1417-
*(lpData as *mut i64) = StreamBytesTransferred;
1418-
}
1419-
c::PROGRESS_CONTINUE
1420-
}
14211419
let pfrom = maybe_verbatim(from)?;
14221420
let pto = maybe_verbatim(to)?;
1423-
let mut size = 0i64;
1424-
cvt(unsafe {
1425-
c::CopyFileExW(
1426-
pfrom.as_ptr(),
1427-
pto.as_ptr(),
1428-
Some(callback),
1429-
&mut size as *mut _ as *mut _,
1430-
ptr::null_mut(),
1431-
0,
1432-
)
1433-
})?;
1434-
Ok(size as u64)
1421+
1422+
// NT 4+
1423+
//
1424+
// Unicows implements CopyFileExW similarly to other functions (convert to ANSI, call ...A API).
1425+
// However, 9x/ME don't support CopyFileExA either. This means that we have to check both for
1426+
// the API to exist *and* that we're running on NT.
1427+
if c::CopyFileExW::option().is_some() && compat::is_windows_nt() {
1428+
unsafe extern "system" fn callback(
1429+
_TotalFileSize: c::LARGE_INTEGER,
1430+
_TotalBytesTransferred: c::LARGE_INTEGER,
1431+
_StreamSize: c::LARGE_INTEGER,
1432+
StreamBytesTransferred: c::LARGE_INTEGER,
1433+
dwStreamNumber: c::DWORD,
1434+
_dwCallbackReason: c::DWORD,
1435+
_hSourceFile: c::HANDLE,
1436+
_hDestinationFile: c::HANDLE,
1437+
lpData: c::LPCVOID,
1438+
) -> c::DWORD {
1439+
if dwStreamNumber == 1 {
1440+
*(lpData as *mut i64) = StreamBytesTransferred;
1441+
}
1442+
c::PROGRESS_CONTINUE
1443+
}
1444+
1445+
let mut size = 0i64;
1446+
cvt(unsafe {
1447+
c::CopyFileExW(
1448+
pfrom.as_ptr(),
1449+
pto.as_ptr(),
1450+
Some(callback),
1451+
&mut size as *mut _ as *mut _,
1452+
ptr::null_mut(),
1453+
0,
1454+
)
1455+
})?;
1456+
Ok(size as u64)
1457+
} else {
1458+
// NT 3.51 and earlier, or 9x/ME
1459+
1460+
// If `CopyFileExW` is not available, we have to copy the file with the non-Ex API,
1461+
// then open it with `dwDesiredAccess = 0` (query attributes only),
1462+
// then use `GetFileSize` to retrieve the size
1463+
cvt(unsafe {
1464+
c::CopyFileW(
1465+
pfrom.as_ptr(),
1466+
pto.as_ptr(),
1467+
c::FALSE, // FALSE: allow overwriting
1468+
)
1469+
})?;
1470+
1471+
let handle = unsafe {
1472+
c::CreateFileW(
1473+
pto.as_ptr(),
1474+
0,
1475+
c::FILE_SHARE_READ | c::FILE_SHARE_WRITE,
1476+
ptr::null_mut(),
1477+
c::OPEN_EXISTING,
1478+
0,
1479+
ptr::null_mut(),
1480+
)
1481+
};
1482+
1483+
let handle = if let Ok(handle) =
1484+
OwnedHandle::try_from(unsafe { HandleOrInvalid::from_raw_handle(handle) })
1485+
{
1486+
handle
1487+
} else {
1488+
return Err(Error::last_os_error());
1489+
};
1490+
1491+
let mut upper_u32: u32 = 0;
1492+
let lower_u32 = unsafe { c::GetFileSize(handle.as_raw_handle(), &mut upper_u32) };
1493+
1494+
// 0xFFFFFFFF might be a valid length, so we have to check GetLastError
1495+
if lower_u32 == c::INVALID_FILE_SIZE {
1496+
let error = api::get_last_error();
1497+
if error.code != c::ERROR_SUCCESS {
1498+
return Err(Error::from_raw_os_error(error.code as i32));
1499+
}
1500+
}
1501+
1502+
Ok((upper_u32 as u64) << 32 | lower_u32 as u64)
1503+
}
14351504
}
14361505

14371506
#[allow(dead_code)]

0 commit comments

Comments
 (0)