Skip to content

Commit

Permalink
Add fallback implementation for CopyFileExW
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
seritools authored and mbilker committed Sep 16, 2023
1 parent fcbffcb commit ea62d85
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 41 deletions.
21 changes: 21 additions & 0 deletions library/std/src/sys/windows/c.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<'_>,
Expand Down Expand Up @@ -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! {
Expand Down Expand Up @@ -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;
}
1 change: 0 additions & 1 deletion library/std/src/sys/windows/c/windows_sys.lst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 0 additions & 11 deletions library/std/src/sys/windows/c/windows_sys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
110 changes: 81 additions & 29 deletions library/std/src/sys/windows/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -1419,36 +1419,88 @@ 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,
_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)]
Expand Down

0 comments on commit ea62d85

Please sign in to comment.