diff options
author | Barney Gale <barney.gale@gmail.com> | 2024-06-19 00:59:54 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-06-19 00:59:54 (GMT) |
commit | 20d5b84f57a6f7e5a76109e483abe39d9f703d56 (patch) | |
tree | db0e439b59968cd5fcff982e13f1b155ed3f0a80 /Lib/pathlib/_os.py | |
parent | 9f741e55c16376412c1473aa45b94314c00a0c43 (diff) | |
download | cpython-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.py | 27 |
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 |