From 0bb64a8fae800fbe8a3354d20ac41d868be99837 Mon Sep 17 00:00:00 2001 From: Dennis Duda Date: Thu, 28 Dec 2023 20:13:14 +0100 Subject: [PATCH] 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`. --- library/std/src/sys/windows/c.rs | 11 ++ library/std/src/sys/windows/c/windows_sys.lst | 5 + library/std/src/sys/windows/c/windows_sys.rs | 13 ++ library/std/src/sys/windows/fs.rs | 131 +++++++++++++----- 4 files changed, 129 insertions(+), 31 deletions(-) diff --git a/library/std/src/sys/windows/c.rs b/library/std/src/sys/windows/c.rs index eafad2e3b981a..c7c060324e3a7 100644 --- a/library/std/src/sys/windows/c.rs +++ b/library/std/src/sys/windows/c.rs @@ -453,6 +453,17 @@ compat_fn_lazy! { cchfilepath: u32, dwflags: GETFINALPATHNAMEBYHANDLE_FLAGS, ) -> u32; + + // >= NT 4+; partial: 95+ (provided by unicows, returns "not implemented") + // 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 BOOL, + dwcopyflags: u32, + ) -> BOOL; } compat_fn_optional! { diff --git a/library/std/src/sys/windows/c/windows_sys.lst b/library/std/src/sys/windows/c/windows_sys.lst index 34d79d4f7c9e5..5c9a60f6a3bbe 100644 --- a/library/std/src/sys/windows/c/windows_sys.lst +++ b/library/std/src/sys/windows/c/windows_sys.lst @@ -2626,3 +2626,8 @@ Windows.Win32.Networking.WinSock.WSADuplicateSocketA // NT vs 9x compat Windows.Win32.System.SystemInformation.GetVersion + +// file ops fallbacks +Windows.Win32.Storage.FileSystem.CopyFileW +Windows.Win32.Storage.FileSystem.GetFileSize +Windows.Win32.Storage.FileSystem.INVALID_FILE_SIZE diff --git a/library/std/src/sys/windows/c/windows_sys.rs b/library/std/src/sys/windows/c/windows_sys.rs index 1a9b36d74176b..bf723fac75dc5 100644 --- a/library/std/src/sys/windows/c/windows_sys.rs +++ b/library/std/src/sys/windows/c/windows_sys.rs @@ -68,6 +68,14 @@ extern "system" { ) -> BOOL; } #[link(name = "kernel32")] +extern "system" { + pub fn CopyFileW( + lpexistingfilename: PCWSTR, + lpnewfilename: PCWSTR, + bfailifexists: BOOL, + ) -> BOOL; +} +#[link(name = "kernel32")] extern "system" { pub fn CreateDirectoryW( lppathname: PCWSTR, @@ -318,6 +326,10 @@ extern "system" { ) -> BOOL; } #[link(name = "kernel32")] +extern "system" { + pub fn GetFileSize(hfile: HANDLE, lpfilesizehigh: *mut u32) -> u32; +} +#[link(name = "kernel32")] extern "system" { pub fn GetFileType(hfile: HANDLE) -> FILE_TYPE; } @@ -3610,6 +3622,7 @@ impl ::core::clone::Clone for INIT_ONCE { } pub const INIT_ONCE_INIT_FAILED: u32 = 4u32; pub const INVALID_FILE_ATTRIBUTES: u32 = 4294967295u32; +pub const INVALID_FILE_SIZE: u32 = 4294967295u32; pub const INVALID_SET_FILE_POINTER: u32 = 4294967295u32; pub const INVALID_SOCKET: SOCKET = -1i32 as _; #[repr(C)] diff --git a/library/std/src/sys/windows/fs.rs b/library/std/src/sys/windows/fs.rs index f9dfdbf9a5be7..fd32e522b8bd1 100644 --- a/library/std/src/sys/windows/fs.rs +++ b/library/std/src/sys/windows/fs.rs @@ -17,7 +17,7 @@ use crate::sys_common::{AsInner, FromInner, IntoInner}; use crate::thread; use super::path::maybe_verbatim; -use super::{api, to_u16s, IoResult}; +use super::{api, compat, to_u16s, IoResult}; pub struct File { handle: Handle, @@ -1098,8 +1098,22 @@ pub fn unlink(p: &Path) -> io::Result<()> { pub fn rename(old: &Path, new: &Path) -> io::Result<()> { let old = maybe_verbatim(old)?; let new = maybe_verbatim(new)?; - cvt(unsafe { c::MoveFileExW(old.as_ptr(), new.as_ptr(), c::MOVEFILE_REPLACE_EXISTING) })?; - Ok(()) + let res = + cvt(unsafe { c::MoveFileExW(old.as_ptr(), new.as_ptr(), c::MOVEFILE_REPLACE_EXISTING) }); + + match res { + Err(e) if e.raw_os_error() == Some(c::ERROR_CALL_NOT_IMPLEMENTED as i32) => { + // 9x/ME doesn't support MoveFileEx, so we fall back to copy + delete and hope for the + // best + unsafe { + cvt(c::CopyFileW(old.as_ptr(), new.as_ptr(), c::TRUE))?; + cvt(c::DeleteFileW(old.as_ptr()))?; + Ok(()) + } + } + Err(e) => Err(e), + Ok(_) => Ok(()), + } } pub fn rmdir(p: &Path) -> io::Result<()> { @@ -1402,36 +1416,91 @@ 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+ + // + // Unicows implements CopyFileExW similarly to other functions (convert to ANSI, call ...A API). + // However, 9x/ME don't support CopyFileExA either. This means that we have to check both for + // the API to exist *and* that we're running on NT. + if c::CopyFileExW::option().is_some() && compat::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(unsafe { HandleOrInvalid::from_raw_handle(handle) }) + { + handle + } else { + return Err(Error::last_os_error()); + }; + + let mut upper_u32: u32 = 0; + let lower_u32 = unsafe { c::GetFileSize(handle.as_raw_handle(), &mut upper_u32) }; + + // 0xFFFFFFFF might be a valid length, so we have to check GetLastError + if lower_u32 == c::INVALID_FILE_SIZE { + let error = api::get_last_error(); + if error.code != c::ERROR_SUCCESS { + return Err(Error::from_raw_os_error(error.code as i32)); + } + } + + Ok((upper_u32 as u64) << 32 | lower_u32 as u64) + } } #[allow(dead_code)]