GH-73991: Support copying directory symlinks on older Windows (#120807)

Check for `ERROR_INVALID_PARAMETER` when calling `_winapi.CopyFile2()` and
raise `UnsupportedOperation`. In `Path.copy()`, handle this exception and
fall back to the `PathBase.copy()` implementation.
This commit is contained in:
Barney Gale 2024-07-03 04:30:29 +01:00 committed by GitHub
parent 089835469d
commit f09d184821
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 40 additions and 29 deletions

View file

@ -20,6 +20,15 @@ except ImportError:
_winapi = None
__all__ = ["UnsupportedOperation"]
class UnsupportedOperation(NotImplementedError):
"""An exception that is raised when an unsupported operation is attempted.
"""
pass
def get_copy_blocksize(infd):
"""Determine blocksize for fastcopying on Linux.
Hopefully the whole file will be copied in a single call.
@ -106,18 +115,30 @@ if _winapi and hasattr(_winapi, 'CopyFile2') and hasattr(os.stat_result, 'st_fil
Copy from one file to another using CopyFile2 (Windows only).
"""
if follow_symlinks:
flags = 0
_winapi.CopyFile2(source, target, 0)
else:
# Use COPY_FILE_COPY_SYMLINK to copy a file symlink.
flags = _winapi.COPY_FILE_COPY_SYMLINK
try:
_winapi.CopyFile2(source, target, flags)
return
except OSError as err:
# Check for ERROR_ACCESS_DENIED
if err.winerror != 5 or not _is_dirlink(source):
if err.winerror == 5 and _is_dirlink(source):
pass
else:
raise
# Add COPY_FILE_DIRECTORY to copy a directory symlink.
flags |= _winapi.COPY_FILE_DIRECTORY
_winapi.CopyFile2(source, target, flags)
try:
_winapi.CopyFile2(source, target, flags)
except OSError as err:
# Check for ERROR_INVALID_PARAMETER
if err.winerror == 87:
raise UnsupportedOperation(err) from None
else:
raise
else:
copyfile = None