|
42 | 42 | """ |
43 | 43 | import datetime |
44 | 44 | import difflib |
| 45 | +import filecmp |
45 | 46 | import glob |
46 | 47 | import hashlib |
47 | 48 | import inspect |
48 | 49 | import itertools |
49 | 50 | import os |
| 51 | +import platform |
50 | 52 | import re |
51 | 53 | import shutil |
52 | 54 | import signal |
|
61 | 63 | import urllib.request as std_urllib |
62 | 64 |
|
63 | 65 | from easybuild.base import fancylogger |
| 66 | +from easybuild.tools import LooseVersion |
64 | 67 | # import build_log must stay, to use of EasyBuildLog |
65 | 68 | from easybuild.tools.build_log import EasyBuildError, EasyBuildExit, CWD_NOTFOUND_ERROR |
66 | 69 | from easybuild.tools.build_log import dry_run_msg, print_msg, print_warning |
@@ -2427,8 +2430,42 @@ def copy_file(path, target_path, force_in_dry_run=False): |
2427 | 2430 | else: |
2428 | 2431 | mkdir(os.path.dirname(target_path), parents=True) |
2429 | 2432 | if path_exists: |
2430 | | - shutil.copy2(path, target_path) |
2431 | | - _log.info("%s copied to %s", path, target_path) |
| 2433 | + try: |
| 2434 | + # on filesystems that support extended file attributes, copying read-only files with |
| 2435 | + # shutil.copy2() will give a PermissionError, when using Python < 3.7 |
| 2436 | + # see https://bugs.python.org/issue24538 |
| 2437 | + shutil.copy2(path, target_path) |
| 2438 | + _log.info("%s copied to %s", path, target_path) |
| 2439 | + # catch the more general OSError instead of PermissionError, |
| 2440 | + # since Python 2.7 doesn't support PermissionError |
| 2441 | + except OSError as err: |
| 2442 | + # if file is writable (not read-only), then we give up since it's not a simple permission error |
| 2443 | + if os.path.exists(target_path) and os.stat(target_path).st_mode & stat.S_IWUSR: |
| 2444 | + raise EasyBuildError("Failed to copy file %s to %s: %s", path, target_path, err) |
| 2445 | + |
| 2446 | + pyver = LooseVersion(platform.python_version()) |
| 2447 | + if pyver >= LooseVersion('3.7'): |
| 2448 | + raise EasyBuildError("Failed to copy file %s to %s: %s", path, target_path, err) |
| 2449 | + elif LooseVersion('3.7') > pyver >= LooseVersion('3'): |
| 2450 | + if not isinstance(err, PermissionError): |
| 2451 | + raise EasyBuildError("Failed to copy file %s to %s: %s", path, target_path, err) |
| 2452 | + |
| 2453 | + # double-check whether the copy actually succeeded |
| 2454 | + if not os.path.exists(target_path) or not filecmp.cmp(path, target_path, shallow=False): |
| 2455 | + raise EasyBuildError("Failed to copy file %s to %s: %s", path, target_path, err) |
| 2456 | + |
| 2457 | + try: |
| 2458 | + # re-enable user write permissions in target, copy xattrs, then remove write perms again |
| 2459 | + adjust_permissions(target_path, stat.S_IWUSR) |
| 2460 | + shutil._copyxattr(path, target_path) |
| 2461 | + adjust_permissions(target_path, stat.S_IWUSR, add=False) |
| 2462 | + except OSError as err: |
| 2463 | + raise EasyBuildError("Failed to copy file %s to %s: %s", path, target_path, err) |
| 2464 | + |
| 2465 | + msg = ("Failed to copy extended attributes from file %s to %s, due to a bug in shutil (see " |
| 2466 | + "https://bugs.python.org/issue24538). Copy successful with workaround.") |
| 2467 | + _log.info(msg, path, target_path) |
| 2468 | + |
2432 | 2469 | elif os.path.islink(path): |
2433 | 2470 | if os.path.isdir(target_path): |
2434 | 2471 | target_path = os.path.join(target_path, os.path.basename(path)) |
|
0 commit comments