summaryrefslogtreecommitdiffstats
path: root/Lib/pathlib/_os.py
diff options
context:
space:
mode:
authorBarney Gale <barney.gale@gmail.com>2024-06-19 00:59:54 (GMT)
committerGitHub <noreply@github.com>2024-06-19 00:59:54 (GMT)
commit20d5b84f57a6f7e5a76109e483abe39d9f703d56 (patch)
treedb0e439b59968cd5fcff982e13f1b155ed3f0a80 /Lib/pathlib/_os.py
parent9f741e55c16376412c1473aa45b94314c00a0c43 (diff)
downloadcpython-20d5b84f57a6f7e5a76109e483abe39d9f703d56.zip
cpython-20d5b84f57a6f7e5a76109e483abe39d9f703d56.tar.gz
cpython-20d5b84f57a6f7e5a76109e483abe39d9f703d56.tar.bz2
GH-73991: Add follow_symlinks argument to `pathlib.Path.copy()` (#120519)
Add support for not following symlinks in `pathlib.Path.copy()`. On Windows we add the `COPY_FILE_COPY_SYMLINK` flag is following symlinks is disabled. If the source is symlink to a directory, this call will fail with `ERROR_ACCESS_DENIED`. In this case we add `COPY_FILE_DIRECTORY` to the flags and retry. This can fail on old Windowses, which we note in the docs. No news as `copy()` was only just added.
Diffstat (limited to 'Lib/pathlib/_os.py')
-rw-r--r--Lib/pathlib/_os.py27
1 files changed, 24 insertions, 3 deletions
diff --git a/Lib/pathlib/_os.py b/Lib/pathlib/_os.py
index 1771d54..bbb019b 100644
--- a/Lib/pathlib/_os.py
+++ b/Lib/pathlib/_os.py
@@ -4,6 +4,7 @@ Low-level OS functionality wrappers used by pathlib.
from errno import EBADF, EOPNOTSUPP, ETXTBSY, EXDEV
import os
+import stat
import sys
try:
import fcntl
@@ -91,12 +92,32 @@ else:
copyfd = None
-if _winapi and hasattr(_winapi, 'CopyFile2'):
- def copyfile(source, target):
+if _winapi and hasattr(_winapi, 'CopyFile2') and hasattr(os.stat_result, 'st_file_attributes'):
+ def _is_dirlink(path):
+ try:
+ st = os.lstat(path)
+ except (OSError, ValueError):
+ return False
+ return (st.st_file_attributes & stat.FILE_ATTRIBUTE_DIRECTORY and
+ st.st_reparse_tag == stat.IO_REPARSE_TAG_SYMLINK)
+
+ def copyfile(source, target, follow_symlinks):
"""
Copy from one file to another using CopyFile2 (Windows only).
"""
- _winapi.CopyFile2(source, target, 0)
+ if follow_symlinks:
+ flags = 0
+ else:
+ 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):
+ raise
+ flags |= _winapi.COPY_FILE_DIRECTORY
+ _winapi.CopyFile2(source, target, flags)
else:
copyfile = None